Android 中View的绘制机制源码分析 四

到目前为止,我们已经学习了View的测量,布局过程,今天我们就来学习一下最后一个过程:绘画
绘画过程和前面的两个过程一样,都是在ViewRoot的performTraversals这个方法中调用的,感兴趣的同学可以找找看,我这里就不在贴出代码了,我们直接看View的draw方法吧

View的draw过程的五部曲


    /** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, do not override this method; instead, * you should implement {@link #onDraw}. * * @param canvas The Canvas to which the View is rendered. */
    public void draw(Canvas canvas) {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
        }

        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~DIRTY_MASK) | 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) {
            final Drawable background = mBGDrawable;
            if (background != null) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;

                if (mBackgroundSizeChanged) {
                    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                    mBackgroundSizeChanged = false;
                }

                if ((scrollX | scrollY) == 0) {
                    background.draw(canvas);
                } else {
                    canvas.translate(scrollX, scrollY);
                    background.draw(canvas);
                    canvas.translate(-scrollX, -scrollY);
                }
            }
        }

        // 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);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            // we're done...
            return;
        }

        /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;
        int paddingTop = mPaddingTop;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
            paddingTop += getTopPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + paddingTop;
        int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        int length = scrollabilityCache.fadingEdgeLength;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength >= 0.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength >= 0.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength >= 0.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength >= 0.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
    }

该方法比较长,但是内部的执行流程正如源码中注释的,主要分为5步:

  1. 绘制背景,这里使用了dirtyOpaque表示dirty区是否是不透明的,如果是透明的,那么绘制背景,在Android系统中,基本上dirtyOpaque基本上都是false的,所以背景区基本上都会绘制
  2. 绘制视图的渐变框,但是大多数情况下都不需要绘制渐变框,因此这里直接跳过
  3. 绘制视图本身,绘制视图本身调用的是onDraw() 方法,View的设计者可以在onDraw() 方法中根据自己的需要,绘制内容
  4. 调用dispatchDraw() 方法绘制子视图,如果视图内没有子视图,则不需要绘制,也就是说自由ViewGroup 的子类需要重载dispatchDraw 方法
  5. 调用onDrawScrollbars() 方法绘制滚动条,当然前提是需要显示滚动条
    经过上面的5个步骤,View的绘制过程差不多就结束了。这里我们重点要关注第3步和第4步,因为我们在自定义View或者ViewGroup的时候基本上就是改写这两步中涉及到的方法

现在View的绘制机制就算讲解完了,接下来我们讲解LayoutParams这个类的相关知识吧

LayoutParams你必须知道的秘密

LayoutParams这个类相信对于每个Android开发人员是再熟悉不过了,但是并不是每个人对这个类都有深入的理解。今天我们就来学习一下LayoutParams你必须知道的秘密。
先来看看官方对于这个类的解释:

LayoutParams are used by views to tell their parents how they want to be laid out. See ViewGroup Layout Attributes for a list of all child view attributes that this class supports.

这个类用于子视图告诉他们的父视图,他们想要怎么显示出来,也就是告诉父类自己的一些属性。
先从一个简单的例子开始:向LinearLayotu中添加一个View
布局文件如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/line_parent" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_child" android:layout_height="match_parent" android:layout_width="match_parent" android:background="#ffccee" android:text="hello my name is yuanzeyao" ></TextView>

代码如下:

  LinearLayout parent= (LinearLayout) this.findViewById(R.id.line_parent);
        View view= LayoutInflater.from(this).inflate(R.layout.layout,null);
        parent.addView(view);

运行效果如下:
《Android 中View的绘制机制源码分析 四》

代码很简单,就是先使用LayoutInflater生成一个View,然后添加到LinearLayout中去,注意这里我们并没有传递LayoutParams相关类型进去的,那View是如何告诉LinearLayout它的LayoutParams呢。我们进入到addView方法看看,你会发现最终调用的是ViewGroup中的如下方法

 public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

代码比较简单,先通过getLayoutParams方法拿到LayoutParams对象,如果此对象不为空,那么直接调用addView(child,index,params) 如果为空,那么久调用generateDefaultLayoutParams方法生成一个LayoutParams对象,我们进入源码看看ViewGroup中的generaterDefaultLayoutParams 方法是如何生存的一个LayoutParams的吧

  protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

这里就是简单的创建了一个ViewGroup.Layoutparams对象,但是要知道我们这里是调用的LinearLayout的generateDefaultLayoutParams方法,所以我们看看LinearLayout是如何改写此方法的。

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }

LinearLayout里面的这个方法也很简单,唯一不同的就是它创建的是LinearLayout.LayoutParams类的这个对象,那么LinearLayout.LayoutParams和ViewGroup.LayoutParams有什么关系呢?同样,看看源码就知道了。通过查看源码你会发现,LinearLayout.LayoutParams这个类是继承自ViewGroup.MarginLayoutParams这个类,而这个类又继承了ViewGroup.LayoutParams这个类,其实LinearLayout,RelativeLayout,FrameLayout等布局里面的LayoutParams都是继承自ViewGroup.MarginLayoutParams,继承关系如下:
《Android 中View的绘制机制源码分析 四》

其实通过名称,我们就大概可以猜出ViewGroup.LayoutParams和ViewGroup.MarginLayoutParmas的区别了,那就是MarginLayoutParams支持margin属性了,也就是我们在xml文件中配置的leftMargin、topMargin等属性。我们看看源码是不是真的是这样

public MarginLayoutParams(Context c, AttributeSet attrs) {
            super();

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_MarginLayout_layout_width,
                    R.styleable.ViewGroup_MarginLayout_layout_height);

            int margin = a.getDimensionPixelSize(
                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
            if (margin >= 0) {
                leftMargin = margin;
                topMargin = margin;
                rightMargin= margin;
                bottomMargin = margin;
            } else {
                leftMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft, 0);
                topMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginTop, 0);
                rightMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0);
                bottomMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0);
            }

            a.recycle();
        }

果不其然,就是在解析leftMargin,topMargin等属性。看完了MarginLayoutParams的功能后,我们看看LinearLayout.LayoutParams这个类在父类的基础上增加了上面功能

    public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =
                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }

主要添加了两个属性:weight属性和gravity属性,我们再看看RelatvieLayout.LayoutParams里面增加了上面属性吧

     public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs,
                    com.android.internal.R.styleable.RelativeLayout_Layout);

            final int[] rules = mRules;

            final int N = a.getIndexCount();
            for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                switch (attr) {
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
                        alignWithParent = a.getBoolean(attr, false);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
                        rules[LEFT_OF] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
                        rules[RIGHT_OF] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
                        rules[ABOVE] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
                        rules[BELOW] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
                        rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
                        rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
                        rules[ALIGN_TOP] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
                        rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
                        rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
                        rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
                        rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
                        rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
                        rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
                        rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
                        rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
                        rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
                       break;
                }
            }

            a.recycle();
        }

在RelativeLayout.LayoutParams中添加的属性比较多了,主要是关于对齐方式的,不过你要注意喽,在RelativeLayout.LayoutParams中并没有weight属性和gravity属性。看到这里我想你应该知道为什么LineayLayout不支持对齐方式属性,RelativeLayot不支持gravity和weight属性了。

但是在LinearLayout.gener的generateDefaultLayoutParams 方法中调用的是

public LayoutParams(int width, int height) {
            super(width, height);
            weight = 0;
        }

这个方法,其中默认weight=0,gravity=-1。

看到这里,大家是不是还有一个疑问,在上面的addView() 中,调用child.getLayoutParams() 方法返回是否为null,我们打印一下log就知道,代码如下:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout parent= (LinearLayout) this.findViewById(R.id.line_parent);
        View view= LayoutInflater.from(this).inflate(R.layout.layout,null);
        Log.d("yzy","LayoutParams is null:"+(view.getLayoutParams()==null));
        parent.addView(view);
    }

结果:

07-25 14:48:11.842  26143-26143/? D/yzy﹕ LayoutParams is null:true

也就是说调用的是LinearLayout的generateDefaultLayoutParams方法。
在这里不知道大家发现了一个问题没,我的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_child" android:layout_height="match_parent" android:layout_width="match_parent" android:background="#ffccee" android:text="hello my name is yuanzeyao" ></TextView>

但是TextView并不是全屏的,这是因为我们在加入TextView的时候并没有传入LayoutParams这个参数,而是通过generateDefaultLayoutParams创建的一个默认的LayoutParams

new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);

,那么在什么时候addView 的时候getLayoutParams 这个返回值不等于null,这就得看看这个View是怎么来的了,下面我们就来分析View的创建过程。

LayoutInflater创建View过程分析

通过LayoutInflater创建View其实就是调用inflate 方法,我们看看此方法的原型吧

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy.
     * @return The root View of the inflated hierarchy. If root was supplied,
     *         this is the root View; otherwise it is the root of the inflated
     *         XML file.
     */
    public View inflate(int resource, ViewGroup root) {
 return inflate(resource, root, root != null);
    }

此方法有两个参数
resource :表示布局文件id
root : 创建的View的父视图
它的实现也是非常的简单,调用的是另外一个inflate的方法,代码如下:

 /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
 return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

这里多了一个bool参数,此参数的意思是,是否将创建的View add到制定的root父视图上,对inflate(int resource,ViewGroup root) 这个方法来说,它其实是调用inflate(int resource,ViewGroup root,bool attach) 这个方法,并且只要root 不等于null,那么创建的View就会add到root上,并返还root,也就是说root不等于null,则第三个参数就是true。看到这里,我们回头看看我们在创建View的时候,我们传入的第二个参数是null,我们不妨设置为LinearLayout试试,代码如下:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout parent= (LinearLayout) this.findViewById(R.id.line_parent);
        View view= LayoutInflater.from(this).inflate(R.layout.layout,parent,false);
        Log.d("yzy","LayoutParams is null:"+(view.getLayoutParams()==null));
        parent.addView(view);
    }

这里我们将第三个参数设置为false,因为如果不设置,默认是true的,我们看看运行结果吧:
《Android 中View的绘制机制源码分析 四》
看到没,这里的TextView是全屏的,那如果我们把第三个参数设置为true,并且去掉addView方法试试,代码如下:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout parent= (LinearLayout) this.findViewById(R.id.line_parent);
        View view= LayoutInflater.from(this).inflate(R.layout.layout,parent,true);
        Log.d("yzy","LayoutParams is null:"+(view.getLayoutParams()==null));
        //parent.addView(view);
    }

结果:
《Android 中View的绘制机制源码分析 四》
我们发现,结果和上面的一样,想知道为什么是这样的吗,那么继续往下看吧,看看inflate里面到底干了上面,经过不断的跟踪,你会发现最终调用到了的是这个方法:


    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     * <p>
     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
     * reasons, view inflation relies heavily on pre-processing of XML files
     * that is done at build time. Therefore, it is not currently possible to
     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
     * 
     * @param parser XML dom node containing the description of the view
     *        hierarchy.
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            mConstructorArgs[0] = mContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, attrs);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp = createViewFromTag(name, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    // Inflate all children under temp
                    rInflate(parser, temp, attrs);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            }

            return result;
        }
    }

这个方法看起来比较长,但是逻辑非常简单,主要有如下步骤:

  1. 创建View ,通过调用createViewFromTag 方法创建View
  2. 如果root不等于null,则调用root的generateLayoutParams 方法,如果第三个参数为false,并将结果设置到上面创建的View中
  3. 调用rInflate 对View的子View进行解析,此时View就充当了root的角色,也就是说在后面的遍历中第二个参数不为null,第三个参数为true
  4. 最后如果root!=null && attach ==true,那么调用root的addView方法root.addView(temp, params) 其中params就是第二部创建的。

最后就是如果root!=null && attach==true,那么返回root,否则返回第一步创建的View。

好了,到这里所有有关View的绘制的机制都已经学习完了,有什么没有写明白的地方欢迎大家留言讨论》。。

    原文作者:yuanzeyao
    原文地址: https://blog.csdn.net/yuanzeyao/article/details/47055017
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞