Application of Android Custom View and Summary of Its Principle Knowledge Points

  • 2021-11-10 10:56:21
  • OfStack

In the development of Android, the UI control provided by the system is limited. When we need to use 1 special control, only the control provided by the system may not achieve the desired effect. At this time, we need to customize 1 control to complete the desired effect. Now, let me talk about custom controls.

First, let me talk about the control architecture of Android. Android controls can be divided into two categories, ViewGroup and View. You can include multiple View in ViewGroup and manage them. The control tree is composed of these two parts. The upper layer of the control tree is responsible for the drawing, measurement and interaction of the lower layer controls. The findViewById () method we use in Activity is to search the corresponding ID by deep traversal in the control tree. At the top of every control tree, there is an ViewParent object, which is the core of the whole tree and is responsible for scheduling all interactive events. In Activity, we use setContentView () to load the layout. Each Activity contains an Window object, which is usually PhoneWindow in Android. It takes an DecorView as the root View of the whole window, and renders the content to be displayed on window. DecorView is divided into two parts, one is TitleView and the other is ContentView. ContentView is an Framelayout where ID is content, and the layout file is set in it. And TitleView is what we see in the topbar title bar. This is how activity loads the layout file.

Next, we begin to talk about the use of custom controls. When we explain the use below, we will carry some analysis of the principles. Custom controls can be divided into three types, one is to expand the system controls provided by Google to achieve the desired effect. One is to combine the controls provided by the system into one and use it as a combined control. Another is to redraw and measure a brand-new control.

1. Expand the system controls provided by Google

If we want to extend the Textview control, we should first define a class to inherit TextView and optionally override its onDraw (), onMeasure (), onTouchEvent () and other methods. Among them, onDraw () is responsible for drawing the image, onMeasure () is responsible for measuring the position, and onTouchEvent () is responsible for setting the touch event. When we want to draw TextView with background color directly, we can define a brush in the class and draw it in onDraw (). The code is as follows:


Paint paint1=new Paint(); // Define Brushes 
paint1.setColor(Color.YELLOW);
paint1.setStyle(Paint.Style.FILL);

Then, through the following code, you can draw a rectangular Textview, but you need to call the parent onDraw () after drawing, because it is expanded on the system control, so you need to have its original function.


@Override
  protected void onDraw(Canvas canvas) {
    canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint1);// Draw a rectangle 
    canvas.save() ; 
    super.onDraw(canvas);
    canvas.restore();
  }

Using the canvas object, you can draw. I will explain canvas in the next blog.

Then, we only need to add a custom control to the layout file. In the layout file, the name of the custom view is the package name of the custom control class plus the class name. Assuming that the CustomTextview class is defined to inherit TextView, the example is as follows:


<com.example.myapplication.View.CustomTextView
    android:layout_width="wrap_content"
    android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>

2. Combine system-supplied controls in 1

In addition to expanding the original control, we can also combine the control into a new control to use. First, we define a new layout file and add Imageview and Textview, the code is as follows.


<ImageView
  android:id="@+id/iv"
  android:layout_width="20dp"
  android:layout_height="20dp"
  android:src="@mipmap/ic_launcher" />
 
<TextView
  android:id="@+id/tv"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginTop="2dp"
  android:text=" Message "
  android:textSize="13sp" />

We then define a class that inherits LinearLayout and initializes the control and layout in the class's constructor.


public void init(Context context) {
    // Specifies the display mode of linear layout, vertical 
    setOrientation(VERTICAL);
    // Set the layout expected by users 
    LayoutParams mLayoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    setLayoutParams(mLayoutParams);
    setGravity(Gravity.CENTER);
    setPadding(4, 4, 4, 4);
    // Set its layout file 
    View mButtonbtnView = LayoutInflater.from(context).inflate(layout.botton_btn_view, this, true);
    mImageView = mButtonbtnView.findViewById(id.iv);
    mTextView = mButtonbtnView.findViewById(id.tv);
  }

Next, its use method and expand the control method 1, directly in the layout file, add the control can be.


<com.example.myapplication.View.Buttonbtn
    android:layout_width="wrap_content"
    android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>

3. Rewrite View to implement new controls

When the native control of the system can't meet our requirements, we can define a new control to complete the required functions. To create a new control, it is necessary to inherit View class, and its difficulty mainly lies in drawing the control and realizing interaction. When inheriting the View class, we also need to override its onDraw (), onMeasure (), onTouchEvent () to implement drawing, measuring, and touching events.

onDraw () drawing is to draw the shape of the control by calling its 1 series of methods on the canvas object.

onMeasure ()

Next, I will talk about onMeasure (). Before drawing View, we need to tell the system how big an View we need to draw and where it is, and that's what onMeasure () does. First, let's understand the three modes of measurement under 1:

EXACTLY: Exact value mode, which is used when specifying the specific value of view.

AT_MOST: Maximum mode, used to set the control to "wrap_content", which changes depending on the child control or content.

UNSPECIFIED: Drawing controls can be as large as they want.

According to the above three modes, we can judge and use them when measuring. First, we override the onMeasure () method of an view. Then get the measurement mode of the control by using MeasureSpec class. MeasureSpec uses bit operation, the upper 2 bits are the measurement mode, and the remaining 30 bits are the measurement size.


@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 
    if (widthMode == MeasureSpec.EXACTLY) {
 
    } else if (widthMode == MeasureSpec.AT_MOST) {
 
    } else if (widthMode == MeasureSpec.UNSPECIFIED) {
 
    }
 
  }

The above code is to define the size of the control by judging the measurement mode. Here, only the width of the control is measured, and the measurement of the height of the control is similar, so it is not explained in detail.

As mentioned earlier, ViewGroup is used to manage controls. When the size of ViewGroup is "wrap_content", it will traverse all its children View to obtain the size of the children View, and then set its own size. The layouts we have used, such as RelativeLayout and LinearLayout, all inherit from ViewGroup, so they also use this method to get their own size.

onTouchEvent ()

onTouchEvent () is what we call a touch event. Because Android mobile phone is a touch screen, when we customize View to touch the screen, we also need a certain processing to complete the interaction. When you override the onTouchEvent method, you can see that you need to pass in the MotionEvent object. We can use this class to set the touch event, and we can also get the position of the touch point. We can use getAction () to get the action of touch events to judge whether to press the screen or move. In the coordinate system of Android, we all know that when the screen of Android is erected, the upper left corner is the origin, the right is the positive direction of x axis, and the downward is the positive direction of y axis. After knowing this, we can obtain the coordinates of touch points by calling getX () and getY () methods to complete some interactive operations.


public boolean onTouchEvent(MotionEvent event) {
    float x;
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
      {
        x=event.getX();
      }
        break;
      case MotionEvent.ACTION_MOVE:
        break;
      case MotionEvent.ACTION_UP:
        break;
    }
    return true;
  }

These are the common rewriting methods of custom controls. By rewriting these methods, we can basically realize a simple custom control. Next, let's understand the principle of the event interception mechanism of the next control.

Analysis of event interception mechanism

As we said earlier, the control structure is a tree structure, and there may be multiple ViewGroup or View in one ViewGroup, so how can touch events be accurately assigned to each View and ViewGroup? Let's assume that there is an ViewGroupA in which an ViewGroupB is nested, and an View is nested in an ViewGroupB. When we override the ViewGroupA class, we need to override these three methods:

dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent()

When you override View, you need to override two methods:

dispatchTouchEvent() onTouchEvent()

As you can see from the name, ViewGroup has more onInterceptTouchEvent () method than View, which is the core of event interception. Under Log1 in every method, when you click View again, you will find the order of method calls:

First, you call dispatchTouchEvent () and onInterceptTouchEvent () of the ViewGroupA class.

The dispatchTouchEvent () and onInterceptTouchEvent () of the ViewGroupB class are called again.

Then to the dispatchTouchEvent () method of View.

The order of this call is the order of event delivery, and the order of event processing is:

onTouchEvent () for View. onTouchEvent () for ViewGroupB. onTouchEvent () for ViewGroupA.

From this, we can see that the distribution of events is issued by the upper ViewGroup, and then distributed layer by layer. The event processing is processed by the lower View, and then uploaded layer by layer. As mentioned earlier, onInterceptTouchEvent () is the core of event interception, so as long as its return value is set to true, events can be intercepted so that they are no longer distributed, while onTouchEvent () returns false, and events will not be uploaded after processing. The process of event distribution and interception is roughly explained.


Related articles: