[Reading notes] [Android] to explore the development of art in Chapter 4 View works

Please indicate the source  http://blog.csdn.net/yxhuang2008/article/details/51126616

First, the basics

1, ViewRoot and DecorView

ViewRoot corresponds ViewRootImpl class, and it is connected WindowManager DecorView ties, View of the three processes are done by ViewRoot. In ActivityThread, after the Activity object is created, it will be added to the Window DecorView while ViewRoot created objects.

DecorView added to the Window's window procedure.


Pictures from  https://yq.aliyun.com/articles/3005


View drawing preformTraversals ViewRootImpl process begins, the following pseudo code is its

 // ViewRootImple#performTraverals 的伪代码
    private void preformTraverals(){
        preformMeasure(...)     --------- View.measure(...);
        
        performLayout(...)        ---------  View.layout(...);
        
        performDraw(...)           --------   View.draw(...);
    }


2、 MeasureSpec

MeasureSpec value is a 32-bit, two bits of SpecMode, measurement mode is the low specsize 30, is in a certain size specifications measurement mode.
(1) .SpecMode classification:
UNSPECIFIED: represents the developer can try to set an arbitrary size as they wish, without any restrictions. This situation is rare, not very goodTo. For example, since the general listViewView the definition of less than.

EXACTLY: parent view expressed hope that the size of the child views should be determined by the value of the SpeceSize. Child elements will be limited to a given boundary in and ignore itSize itself. Corresponding attributeMatch_parent or to a specified size.

WRAP_PARENT: represents the child view can only be SpecSize specified size, small developers should have to set this view as much as possible, andAnd to ensure that no more than SpeceSize.Corresponding property is wrap_content.

(2) .MeasureSpece and correspondence between LayoutParams
DecorView MeasureSpec determined by the window size and LayoutParams for itself;
Normal View, MeasureSpec determined by MeasureSpce parent container and its LayoutParams.
ViewGroup of the getChildMeasureSpece (...) method, can be drawn under the drawing conclusions.


Two, View workflow

1, measure process

(1). Measure of core methods
      .mensure(int widthMeasureSpec, int heightMeasureSpec)
 The method as defined in View final, this method can not be overridden. But the measure will eventually call onMeasure (...) method, so the custom of View, as long rewriteonMeasure (...) method can be.

.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

This method is our custom View only when the need to implement the measure to draw logical method, the parameters of this method is widht parent view and the child viewA method of measuring the height required.When Custom View, need to do is calculate more widthMeasureSpec and heightMeasureSpecView width and height, various different processing modes.

.setMeasuredDimension(int measuredWidth, int measureHeith)

The method of measuring the final stage, in onMeasure (int widthMeasureSpec, int heightMeasureSpece) method call, and the resulting calculatedPassing the method size, measuredThe amount of phase ends. This method must be called, otherwise it will report an exception. When the Custom View, the system does not require complicated relationshipMeasure the process, simply callsetMeasuredDimension(int measuredWith, int measuredHeith) 设置根据 MeasureSpecTo calculate the size obtained.


(2) measure the process

Measure the size of the process of passing two parameters

ViewGroup.LayoutParams View own layout parameters;

MeasureSpec class, the child view parent view measurement requirements.


View of the measure process


View of getDefaultSize method

    // View#getDefaultSize
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }
As can be seen getDefaultSize method, high View width is determined by the specSize.

The direct successor View custom control needs to be rewritten onMeasure (...) method and set its own size when wrap_content, otherwise use wrap_content equivalent to using match_parent in the layout. LayoutParams from MeasureSpece and relational tables can be seen.

Solution, to specify a default View internal width and height (mWith and mHeight), and the width and height can be provided at this wrap_content.

 protected  void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpeceMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSpeceSize = View.MeasureSpec.getSize(widthMeasureSpec);
        int heightSepceSize = View.MeasureSpec.getSize(heightMeasureSpec);

        if (widthMeasureSpec == MeasureSpec.AT_MOST
                && heightMeasureSpec == MeasureSpec.AT_MOST){
            // 设置一个默认的宽高
             setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == View.MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth, heightSepceSize);
        } else if (heightSpeceMode == View.MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpeceSize, mHeight);
        }
    }


ViewGroup the measure process.

ViewGroup In addition to completing the process that their measure, but also to call the measure method to traverse all the child elements. ViewGroup is an abstractClass, there is no method of rewriting onMeasure of View. ViewGroup does not define specific process measurement, onMeasure method which requires measurement processEach like to achieve.


After completion of measure can be acquired by measuring the width and height of View getMeasureWidth / getMeasureHeight, to the process to acquire onLayout View measured width and height of the final width or higher.

因为 View 的 measure 过程和 Activity 的生命周期方法不是同步的,因此无法保证 Activity 在 onCreate, onStart, onResume 方法中获取 View 的宽高信息。

解决办法:

1. 在 Activity/View # onWindowFoucsChanged 方法中

   @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus){
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    }

2. 使用 view.post(Runnable)

   @Override
    protected void onStart() {
        super.onStart();
        mTextView.post(new Runnable() {
            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

3.使用 ViewTreeObserver

@Override
    protected void onStart() {
        super.onStart();

        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int widht = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

4. 使用 View.measure(int widthMeasureSpec, int heightMeasureSpec)

通过手动对 View 进行 measure 得到 View 的宽高。

 View 的 LayoutParams 分:

match_parent: 

无法测出;


具体数值(dp/px):

     例如宽高都是 100px 时

int widthMesureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
        view.measure(widthMesureSpec, heightMeasureSpec);


wrap_parent 时

        int widthMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST);
        view.measure(widthMesureSpec, heightMeasureSpec);

2. layout 过程

子视图的具体位置是相对于父视图而言的。View 的 onLayout 方法时空方法,ViewGrop 的 onLayout 方法时 abstract .

如果自定义的 View 继承 ViewGroup ,需要实现 onLayout 方法。

 // View#layout
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        // setOpticalFrame / setFrame 设定 View 的四个顶点
        boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 空方法
            onLayout(changed, l, t, r, b);

            ...
        }
        ...
    }


getMeasureWidth 和 getWidth 之间的区别:

getMeasureWidth 是 measure() 过程之后获取后,getWidth 是在 layout() 过程之后得到的。getMeasureWidth() 方法中的值是通过 setMeasureDimension() 方法类进行设置的,而 getWidth() 方法中的值是通过视图右边的坐标减去左边的坐标计算出来的。


3.draw 过程

.View.draw(Canvas canvas)  
ViewGroup 没有重写该方法,所以所有的视图最终都会调用 View 的 draw(...) 方法进行绘制。在自定义视图时,不应该重写该方法,而是应该重写 onDraw(Canvas cavas) 方法,进行绘制。如果自定义视图确实要重写该方法,先调用super.draw(canvas) 完成系统的绘制,然后再进行自定义的绘制。 
.View.onDraw(..) 
View 的 onDraw(...) 方法默认是空方法,自定义视图时,需要重写该方法,绘制自身的内容。
.View.dispatchDraw(...)
View 中默认是空方法,ViewGroup 重写了该方法对子元素进行了绘制。在自定义 ViewGroup 是不应该对该方法进行重写。
   // View#draw
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            // 第一步,绘制背景
            drawBackground(canvas);
        }

        // 正常情况下,跳过第二步和第五步
        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            
            // Step 3, draw the content
            // 第三步, 绘制自身内容
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            // 第四不,绘制子元素
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            // 第六步, 绘制 foreground, scrollbars
            onDrawForeground(canvas);

            // we're done...
            return;
        }
    }
不需要绘制 layer 的时候会跳过第二步和第五步。因此在绘制的时候,能不绘制 layer 就尽量不绘制 layer, 以提高绘制效率。

setWillNotDraw

   /**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

如果一个 View 不需要绘制任何内容,则将这个标志位设置为 true, 系统会进行相应的优化。当我们在自定义控件继承 ViewGroup 并且本身不具备绘制功能时,可以开启这个标志位从而便于系统进行后续的优化。


invalidate()

请求重绘 View 树,即 draw 过程。假如视图没有发生大小变化就不会调用 layout() 过程,并且只调用那些调用了 invalidate() 方法的 View.


requestLayout()

当布局发生变化时,会调用该方法。在自定义视图中,如果在某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它 会触发 measure() 和 layout() 过程,但不会进行 draw。


三、自定义 View

1、分类

( 1). 继承 View 重写 onDraw() 方法
用来实现一些不规则的视图,需要自己支持 wrap_content, 并且也需要自己处理 padding.

  (2).继承 ViewGroup 派生特殊的 Layout
用于自定义布局,需要合适地处理 ViewGroup 的测量、布局的过程,并同时处理处理子元素的测量和布局过程。

  (3). 继承特定的 View 
用户扩展某种已有的 View 的特性。

  (4).继承特定的 ViewGroup (例如 LinearLayout)

 2、注意事项

(1).让 View 支持 wrap_content
(2).如有必要,让 View 支持 padding, 在 draw 方法中支持处理 padding.
(3).尽量不要在 View 中使用 Handler, 可使用其内部的 post 方法.
(4).View 中有线程或者动画时
在 onAttachToWindow 中启动线程和动画;
在 onDetachFromWindow 方法中要停止线程和动画.
(5). View 带有滑动嵌套情况时,需要处理好滑动冲突。



本文除了是《Android 开发艺术探索》 书中的知识,还有部分内容摘自这两个博客














发布了58 篇原创文章 · 获赞 20 · 访问量 9万+

Guess you like

Origin blog.csdn.net/yxhuang2008/article/details/51126616