Android Custom view What You Need to Know

Keywords: Android Google iOS Attribute

The knowledge about Android's customized view is an unavoidable knowledge module for both interviews and in-depth study. Some of the open source controls we use are hundreds of lines of code, which makes it difficult for us to customize the view. But when we read articles like Guo Lin, Ren Yugang, a well-known blogger, explaining customized view, etc. We will feel that it is not difficult to customize the view. The following article will explain several difficulties in this regard, hoping to help you.

First of all, we should understand Android's event distribution mechanism. This article only makes a concise summary. The specific source code also needs readers to see the relevant blog analysis. What we need to know is that when we click on a view control button in the interface, such a call relationship actually happens inside it. When we register onTouch and onClick events at the same time, the control clicks on the event will go to the top parent view and call the dispatchTouchEvent method, which is the premise of all click events.

    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

Through this method, we can know that the onTouchEvent method will not be invoked when the three conditions are true at the same time. In fact, it will not execute the onClick method. So we can conclude that the implementation of the onClick method is closely related to the onTouchEvent. The main source code of onTouchEvent is:

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
            //-------------------
                break;
            case MotionEvent.ACTION_DOWN:
           //-------------------
                break;
            case MotionEvent.ACTION_CANCEL:
          //-------------------
                break;
            case MotionEvent.ACTION_MOVE:
          //-------------------
                break;
        }
        return true;
    }
    return false;

When the control is clickable, it enters the judgment condition. When we lift our finger and leave the screen, we go to MotionEvent.ACTION_UP. In fact, our registered onClick method is called in this logic. The first point is that all case s here return true, indicating that only every MotionEvent action can continue to execute the next one if it returns true. If either of us returns false, it breaks the rule, and the subsequent action fails to execute. Second point: When our control is not clickable by default, it means that we can't enter the switch event and return false directly, so its up cancel move event, including click event, will not execute. These two knowledge points are very important.

For ViewGroup, which is essentially similar, clicking on view on the ViewGroup layout must first call the dispatchTouchEvent method of ViewGroup, and then find the dispatchTouchEvent method that the view calls, as described above. ViewGroup's dispatchTouchEvent method source code:

        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
        //---------------------------
        //Loop through to find the view and determine if it is the view currently clicked and then call its dispatchTouchEvent method?
         if (child.dispatchTouchEvent(ev))  {
            mMotionTarget = child;
            return true;
            }
        }
        /**
        * ----------------------------
        */
       return super.dispatchTouchEvent(ev); 
        /**
        * ----------------------------
        */

Disallow Intercept can set whether this ViewGroup is masked or not, default is false. SubView can call request Disallow Intercept TouchEvent (true) to modify this property value. The second parameter is to reverse the return value of the onIntercept TouchEvent method. Important conclusion: For viewgroup, if the child view is not intercepted by disallow Intercept on Intercept TouchEvent and dispatch TouchEvent returns true, it will not execute its own touch and click events.

After discussing Android's event distribution mechanism, we need to understand the difference between scrollTo and scrollBy methods and the role of Scroller classes. First, we need to know the coordinate axis of scroll when it moves:

See if it's different from the coordinate system we know. As for why Google engineers set it up like this, it's not clear. What's the difference between scrollTo and scrollBy? Let's look at the source code:

    protected int mScrollX;   //The content of the view corresponds to the offset of the starting coordinate of the view in the X-axis direction.      
    protected int mScrollY;   //The view content corresponds to the offset Y axis direction of the view starting coordinate.  
    //Return value  
    public final int getScrollX() {  
        return mScrollX;  
    }  
    public final int getScrollY() {  
        return mScrollY;  
    }  
    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

The implementation of scrollBy method or calling scrollTo method can be understood as follows: scrollBy is to continue to offset the view content (x, y) units, scrollTo represents the current view content offset to (x, y) coordinates, which is their difference. An image metaphor is equivalent to walking slowly to your home and instantly moving to your home. What do you hear in a flash? One view to another is instantaneous, which is equivalent to ios system without transitional animation. Is the user experience too bad? Yes, our ui interaction experience is really bad. How to solve it? Don't worry about google providing Scroller classes for our developers to solve this problem. There are several important ways to do this:

//Start an animation control, by (startX, startY) in the duration time forward (dx,dy) units, that is, to reach coordinates (startX + dx, startY + dy)
    public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }

This effect is very similar to the viewpager we use. The effect of viewpager sliding should also be controlled by this method. Of course, how can its effect of automatic sliding be realized? Here we can control it by computeScroll(). This method will be called when viewing is drawing, and it is an empty method that needs to be implemented by ourselves. In this computeScroll() method, we need to use the Scroller instance to get its offset and manually scroll to that offset with animation effects. As for the gesture effect of viewpager, we can re-use its onTouchEvent method. We can judge the start, pause, left and right sliding of sliding animation by several actions of MotionEvent, including staying on the original screen, opening the next screen and so on. .

Furthermore, we need to know about the measurement of Android control, specifically the measurement process of view, the layout process of view, and the draw ing process of view. There are too many detailed blog articles about this aspect and many of them are very detailed. This article will not be introduced in detail. The main summary function is that we need to understand the measurement specification of control, MeasureSpec, which is a class of 32 bits. The int type, whose high 2 bits represent the measurement Mode, and the low 30 bits represent the measurement size Size.

  public static class MeasureSpec {

        private static final int MODE_SHIFT = 30;
        //0x3 stands for hexadecimal 3, so MODE_MASK stands for 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        //0000 0000 0000 0000 0000 0000 0000 0000
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        //0100 0000 0000 0000 0000 0000 0000 0000
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        //1000 0000 0000 0000 0000 0000 0000 0000
        public static final int AT_MOST     = 2 << MODE_SHIFT;


        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }


        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }


        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

Its MeasureSpec was created following the rules in the table below (pictures from Ren Yugang's blog):

The measurement specifications of the word view are determined by the measurement specifications of the parent control and the layout of the child control in LayoutParams. The specific rules are as follows. One way to get the width of the View is to manually measure it so that we can write

   //For example, both width and height are 120px ,as follows measure : 
    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(120, MeasureSpec.EXACTLY);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(120, MeasureSpec.EXACTLY);
    view.measure(widthMeasureSpec, heightMeasureSpec);
   //Width and height are wrap_content because size is after30So the maximum of bit representation is(1 << 30) - 1
    int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
    view.measure(widthMeasureSpec, heightMeasureSpec);
   //Width and height with match_parent cannot be measured because the specific size cannot be determined.

When the measurement is completed, onLayout method is invoked to help the children locate. The coordinate points are generally the four values of 0, 0, childView.getMeasuredWidth() and childView.getMeasuredHeight(), and the value of getWidth() method is childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth(), so the value of getWidth() method and getMeasuredWidth() is the phase. Similarly, basically writing getWidth and getMeasuredWidth according to this rule yields the same measured values. If we do not pass in values according to this rule, such as changing the view starting coordinates or passing in custom constant parameters from top button, the getWidth and getMeasuredWidth measured values will be different.

Finally, we need to know some animations commonly used by Android, View Animation, Drawable Animation, Property Animation.
View Animation is also what we usually call Tweened Animation in many books. View Animation can be divided into four categories: Alpha Animation, Rotate Animation, Scale Animation, Translate Animation, and 3.Property Animation corresponds to transparency, rotation, size and displacement. 2.Drawable Animation is an animation (also called Frame animation, Frame animation) that can be categorized into view animation categories and used to display Drawable resources one by one, just like slides. Property animation is only valid for Android 3.0 (API 11) and above versions of Android system. This animation can be set to any Object, including those that have not been rendered to the screen. This animation is extensible and allows you to customize any type and attribute of the animation. Secondly, if you have a thorough understanding of Paint and Canvas, such as arcs, argb and color, Bitmap, circle and oval, point, line, Rect, Picture, Round Rect, text, vertices, path and so on, then congratulations you can say that you have a complete grasp of the writing of custom controls, O( O ha!

Posted by Lucidnight on Sun, 31 Mar 2019 00:48:30 -0700