One, foreword

Android development process custom View is really everywhere, any UI effect, will use a custom View. The previous three articles have covered some case effects of custom View, related classes and apis, as well as the theoretical knowledge of event distribution please charge yourself. The author does not like to speak some of the original rational things, directly on the effect and source code.

This article was originally not about custom views, but the author forced a custom drawing of a small control to match the theme of a recent article. This paper is to achieve the linkage effect of stock and stock list.

Ii. Preparation for development

1. Realize the effect drawing

Insert a picture description here

2. Download the case source code

Click on the download

3. Knowledge points of case application

  1. Custom View basics (Measurement, Canvas, Paint, Path)

  2. HorizontalScrollView Scroll event

  3. RecyclerView nested HorizontalScrollView conflict handling

  4. Interface callback knowledge

  5. Custom layer-list and Shape

4. Case analysis

According to the effect drawing, we can disassemble the layout into the following independent modules:

  1. The overall layout is a Tab column + RecyclerView list

  2. RecyclerView Item layout is consistent with Tab column

  3. When the Tab bar slides horizontally, the RecyclerView list slides synchronously

  4. RecyclerView list item slide, the whole list with scrolling, and Tab column also synchronous scrolling update

Three, code implementation

1. Customize TextView

The basic knowledge of custom View is not reviewed here. If you are not familiar with custom View, you can View the previous article.

Customize TextView, draw the text and small triangle symbol in the upper left corner of the effect picture, and set a background effect. Here the properties are set directly in Java code, and it is recommended to use custom properties to facilitate setting in XML.

1. Measure the TextView size

Measure the width and height of the text according to the size and Padding value of the text. In this case, the size of the custom View is set to WRAP_content in the XML, so look at the MeasureSpec. As for the differences between MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, and MeasureSpec.UNSPECIFIED, check out the author’s previous series of articles on customizable Views.

Reset the View size after the measurement is successful: setMeasuredDimension(Width, height);

/ * ** View size measurement * @param widthMeasureSpec
 * @param heightMeasureSpec
* /
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  super.onMeasure(widthMeasureSpec, heightMeasureSpec);  // Width measurement  width = setMeasureSize(widthMeasureSpec, 1);  // Height measurement  height = setMeasureSize(heightMeasureSpec, 2);  // Set the measured size  setMeasuredDimension(width, height); }  int setMeasureSize(int measureSpec, int type) {  int specSize = 0;  int measurementSize = 0;  int mode = MeasureSpec.getMode(measureSpec);  int size = MeasureSpec.getSize(measureSpec);  switch (mode) {  case MeasureSpec.EXACTLY:// Exact size or maximum  specSize = size;  break;  case MeasureSpec.AT_MOST:  case MeasureSpec.UNSPECIFIED:  if (type == 1) {  measurementSize = rect.width() + getPaddingLeft() + getPaddingRight() + specSize + triangleSize;  } else if (type == 2) {  measurementSize = rect.height() + getPaddingTop() + getPaddingBottom();  }  specSize = Math.min(measurementSize, size);  break;  }  return specSize; } Copy the code

2. Draw text

The Baseline in red is the Baseline, the purple Top is the Top of the text, and the orange Bottom is the Bottom of the text.


So the height of text:

Distance = half the height of the text - distance from the baseline to the bottom of the text (i.e., bottom) =   (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom
Copy the code
// Draw text
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
canvas.drawText(tabStr, getPaddingLeft(), height / 2 + distance, paint);
Copy the code

3. Draw a triangle

Triangles need to be drawn using Path related knowledge, specific related API methods, please learn by yourself.

Draw the canvas. DrawPath (path, paint) method to draw the triangle.

// Draw a triangle
Path path = new Path();
path.moveTo(rect.width() + specSize + getPaddingLeft(), height / 2 - triangleSize / 2);// The lower left corner of the triangle
path.lineTo(rect.width() + specSize + getPaddingLeft(), height / 2 + triangleSize / 2);// The lower right corner of the triangle
path.lineTo(rect.width() + specSize + getPaddingLeft() + triangleSize / 2, height / 2);// Coordinates of the top of the triangle
path.close(); canvas.drawPath(path, paint); Copy the code

4. Define custom View borders

View background is done with layer-list, which is the most common function in daily development. It is often possible to use SHAP to complete some simple background effects, without using images every time, and there will be no trouble in adaptation.

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="@color/tabTextTitle" />
            <corners android:topRightRadius="30dp"  android:bottomRightRadius="30dp"/>  </shape>  </item>  <! -- Set top, bottom, right border only -->  <item  android:bottom="3px"  android:right="3px"  android:top="3px">  <shape android:shape="rectangle">  <solid android:color="#2A2720"/>  <corners android:topRightRadius="30dp"  android:bottomRightRadius="30dp"/>  </shape>  </item> </layer-list> Copy the code

That’s all you need to know about custom views, which is not the focus of this article, but the basics of custom views.

2. Customize CustomizeScrollView

  • Custom CustomizeScrollView inherits the HorizontalScrollView.
  • Override the onScrollChanged() method to listen for ScrollView slides.

  • Define the callback interface OnScrollViewListener to listen for the onScrollChanged() method to scroll back.

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    if(viewListener ! =null) {
        viewListener.onScroll(l, t, oldl, oldt);
 } } Copy the code

The CustomizeScrollView class is simple and doesn’t do much, just reference the full class name in THE XML.

3. Home page layout

Layout XML here is not all posted, more affect the reading of the article, interested friends can download their own research source code, mainly explains the HorizontalScrollView+RecyclerView nesting problem.

If you nested RecyclerView directly in the HorizontalScrollView, the content display will be incomplete when sliding. Many friends have encountered this problem in the development process. (There are 7 items in the Tab column, but it means to slide to the visible item, the following item cannot slide) :


Create a RelativeLayout layout for your HorizontalScrollView. Create a RelativeLayout layout for your HorizontalScrollView. And set properties: android: descendantFocusability = “blocksDescendants”, so that you can perfect solve the problem of incomplete nested to display content.


<com.caobo.stockdemo.view.CustomizeScrollView
    android:id="@+id/headScrollView"
    android:layout_width="0dp"
    android:layout_height="50dp"
 android:layout_weight="Seven">
  <RelativeLayout  android:layout_width="match_parent"  android:layout_height="match_parent"  android:descendantFocusability="blocksDescendants">   <androidx.recyclerview.widget.RecyclerView  android:id="@+id/headRecyclerView"  android:layout_width="match_parent"  android:layout_height="wrap_content" />  </RelativeLayout> </com.caobo.stockdemo.view.CustomizeScrollView> Copy the code

DescendantFocusability: descendantFocusability

BeforeDescendants: A viewgroup takes precedence over its subclass controls to get focusAfterDescendants: A viewgroup gets focus only when its subclass controls do not need itBlocksDescendants: Viewgroup overrides subclass controls to get focus directly.Copy the code

4. Main list Adapter

  1. Once this is done, the rest of the work is done in the adapter of the main list page, defining the ViewHolder collection and recording the sliding X-axis variable:
/ * ** Save the list ViewHolder collection* /
private List<ViewHolder> recyclerViewHolder = new ArrayList<>();
/ * ** Record the position of item sliding to update all lists as RecyclerView scrolls up and down* / private int offestX; Copy the code
  1. The onBindViewHolder() method initializes the data, adds the ViewHolder to the collection, and then scrolls the HorizontalScrollView of the entire list synchronically as you swipe the individual Item horizontally.
/ * ** Step 1: When you swipe the item horizontally, traverse all ViewHolder so that the HorizontalScrollView for the entire list scrolls synchronously* /
holder.mStockScrollView.setViewListener(new CustomizeScrollView.OnScrollViewListener() {
    @Override
 public void onScroll(int l, int t, int oldl, int oldt) {  for (ViewHolder viewHolder : recyclerViewHolder) {  if(viewHolder ! = holder) { viewHolder.mStockScrollView.scrollTo(l, 0);  }  }  } }); Copy the code
  1. Continuing with the previous steps, when sliding the Item horizontally, the interface calls back to the Tab bar HorizontalScrollView, updates the Tab bar scroll position in the MainActivity, And record the sliding X-axis position (used later in RecyclerView to synchronize items).
/ * ** Step 2: When sliding the item horizontally, the interface calls back to the Tab bar's HorizontalScrollView, causing the Tab bar to scroll with the item in real time* /
if(onTabScrollViewListener ! =null) {
    onTabScrollViewListener.scrollTo(l, t);
 offestX = l; } Copy the code
  1. After completing the above steps, it has basically realized the effect of horizontal sliding in RecyclerView list, Tab column and other items synchronous update, followed by the following need to complete Tab horizontal sliding, make RecyclerView synchronous update. Iterate over all holder objects according to ViewHolder set in Adpater and set scrollTo() for each CustomizeScrollView item in RecyclerView. Because horizontal scrolling does not involve the position of the Y-axis, only the X-axis value is set in the cases.
/ * *Step 3: Tab bar HorizontalScrollView when scrolling horizontally, iterate over all RecyclerView lists and make them follow the scrolling* /
headHorizontalScrollView.setViewListener(new CustomizeScrollView.OnScrollViewListener() {
    @Override
 public void onScroll(int l, int t, int oldl, int oldt) {  List<StockAdapter.ViewHolder> viewHolders = mStockAdapter.getRecyclerViewHolder();  for (StockAdapter.ViewHolder viewHolder : viewHolders) {  viewHolder.mStockScrollView.scrollTo(l, 0);  }  } }); Copy the code
  1. This step is to solve a Bug. After the above content is completed, the HOrizontalScrollView is ready to use, but when I scroll up and down the item, I find that the position of the HOrizontalScrollView does not change before it is displayed for the first time. So add addOnScrollListener() to RecyclerView, which will listen as the RecyclerView slides up and down, similar to step 3: iterate over ViewHolder, Gets the X-axis slide position variable OffestX saved in the Adapter to complete the CustomizeScrollView scroll position in the Item.
/ * ** Step 4: RecyclerView vertical sliding, traversal update all item in the HorizontalScrollView of the scrolling position, otherwise the item position does not change state* /
mContentRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {  super.onScrolled(recyclerView, dx, dy);  List<StockAdapter.ViewHolder> viewHolders = mStockAdapter.getRecyclerViewHolder();  for (StockAdapter.ViewHolder viewHolder : viewHolders) {  viewHolder.mStockScrollView.scrollTo(mStockAdapter.getOffestX(), 0);  }  } }); Copy the code

Four,

In fact, custom View is a process that needs to be practiced often. Theoretical knowledge is important, but if you don’t masturbate several cases by yourself, you still can’t master it skillfully. So give a suggestion to friends who learn custom View.

My wechat account is Jaynm888

Welcome to comment, invite Android programmers to join the wechat communication group, public number reply “add group” or add my wechat pull you into the group