美文网首页
Android View的绘制简单分析三

Android View的绘制简单分析三

作者: 梧叶已秋声 | 来源:发表于2020-12-14 17:57 被阅读0次

本篇分析performLayout这一步的过程。
先回顾下ViewRootImpl 。

import android.view.WindowManager;

public class ViewRootImpl {
    private int mWidth = -1;
    private int mHeight = -1;
    private View mView;
    final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

    public void setView(View view) {
        if (mView == null) {
            mView = view;
        }
    }

    void doTraversal() {
        performTraversals();
    }

    private void performTraversals() {
        WindowManager.LayoutParams lp = mWindowAttributes;

        //performMeasure()

        performLayout(lp, mWidth, mHeight);
    }

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        final View host = mView;
        if (host == null) {
            return;
        }
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
}
public class View {
    int mMeasuredWidth;
    int mMeasuredHeight;
    public static final int MEASURED_SIZE_MASK = 0x00ffffff;
  /**
     * The distance in pixels from the left edge of this view's parent
     * to the left edge of this view.
     * {@hide}
     */
    protected int mLeft;
    protected int mRight;
    protected int mTop;
    protected int mBottom;


    protected int mPaddingLeft = 0;
    protected int mPaddingRight = 0;
    protected int mPaddingTop;
    protected int mPaddingBottom;

    protected ViewGroup.LayoutParams mLayoutParams;


    public int mPrivateFlags;

    static final int PFLAG_FORCE_LAYOUT                = 0x00001000;
    static final int PFLAG_LAYOUT_REQUIRED             = 0x00002000;



    final RenderNode mRenderNode;


    public View(Context context) {
        mRenderNode = RenderNode.create(getClass().getName());
    }


    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

    public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

    public void layout(int l, int t, int r, int b) {

        boolean changed =  setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    }




    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        }
        return changed;
    }



    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    public ViewGroup.LayoutParams getLayoutParams() {
        return mLayoutParams;
    }

}

首先需要知道mLeft, mTop, mRight, mBottom。
mLeft是View左上角距父View左侧的距离
mTop是View左上角距父View顶部的距离
mRight是View右下角距父View顶部的距离
mBottom是View右下角距父View左侧的距离

https://www.gcssloop.com/customview/CoordinateSystem

然后还有一个掩码的概念(PFLAG_LAYOUT_REQUIRED)。

掩码(英语:Mask)在计算机学科及数字逻辑中指的是一串二进制数字,通过与目标数字的按位操作,达到屏蔽指定位而实现需求。
创造一个掩码msk把一个指令cmd的第0~3位(右边第一位为0位)清零:
指令cmd = 0110011011
创造掩码msk = 0000001111
用掩码的反码~msk和指令cmd做按位与运算cmd & ~msk = 0110011011 & 1111110000 = 0110010000
则指定的第0~3位已被清零。

RenderNode也是一个概念不太清晰的类。

public class RenderNode {
    public final long mNativeRenderNode;

    private RenderNode(String name) {
        mNativeRenderNode = nCreate(name);
    }

    public static RenderNode create(String name) {
        return new RenderNode(name);
    }

    public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
        return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
    }


   private static native long nCreate(String name);

   private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
                                                         int right, int bottom);

}
//android_view_RenderNode.cpp
namespace android {

using namespace uirenderer;


static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
    RenderNode* renderNode = new RenderNode();
    renderNode->incStrong(0);
    if (name != NULL) {
        const char* textArray = env->GetStringUTFChars(name, NULL);
        renderNode->setName(textArray);
        env->ReleaseStringUTFChars(name, textArray);
    }
    return reinterpret_cast<jlong>(renderNode);
}
// ----------------------------------------------------------------------------
// RenderProperties - setters
// ----------------------------------------------------------------------------

static jboolean android_view_RenderNode_setLeftTopRightBottom(jlong renderNodePtr,
        int left, int top, int right, int bottom) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) {
        renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
        return true;
    }
    return false;
  }

};
//RenderNode.h
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
friend class FrameBuilder;

    void setPropertyFieldsDirty(uint32_t fields) {
        mDirtyPropertyFields |= fields;
    }

 }

RenderNode,render渲染,node,节点。从c文件可知,mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);是将只是将由mLeft, mTop, mRight, mBottom组成的区域存储到mDirtyPropertyFields。

虽然View中的onLayout没有具体实现,但是一般View不重写onLayout。除了TextView中重写了,ImageView,Button等都没有重写。

public class TextView extends View{
    public TextView(Context context) {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mDeferScroll >= 0) {
            int curs = mDeferScroll;
            mDeferScroll = -1;
            bringPointIntoView(Math.min(curs, mText.length()));
        }
        // Call auto-size after the width and height have been calculated.
        autoSizeText();
    }

}
public abstract class ViewGroup extends View{
    // Child views of this ViewGroup
    private View[] mChildren = new View[2];

    public ViewGroup(Context context) {
        super(context);
    }


    public void addView(View child1,View child2) {
        mChildren[0] = child1;
        mChildren[1] = child2;
    }


    protected abstract void onLayout(boolean changed, int l, int t, int r, int b);



    public static class LayoutParams{

        public static final int FILL_PARENT = -1;
        public static final int MATCH_PARENT = -1;
        public static final int WRAP_CONTENT = -2;

        public int width;
        public int height;

        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            this.width = source.width;
            this.height = source.height;
        }

        /**
         * Used internally by MarginLayoutParams.
         * @hide
         */

        LayoutParams() {

        }
    }

    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
        public int leftMargin;
        public int topMargin;
        public int rightMargin;
        public int bottomMargin;

        public MarginLayoutParams(ViewGroup.MarginLayoutParams source) {
            this.width = source.width;
            this.height = source.height;

            this.leftMargin = source.leftMargin;
            this.topMargin = source.topMargin;
            this.rightMargin = source.rightMargin;
            this.bottomMargin = source.bottomMargin;
         }
    }

    View getVirtualChildAt(int index) {
        return getChildAt(index);
    }

    public View getChildAt(int index) {
        if (index < 0 || index >= 2) {
            return null;
        }
        return mChildren[index];
    }
}
public class LinearLayout extends ViewGroup {
    private int mOrientation;
    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;
    private int mGravity = Gravity.START | Gravity.TOP;
    private int mTotalLength;//measure的时候这个值 会具体赋值

    /**
     * Horizontal layout direction is from Left to Right.
     */
    public static final int LTR = 0;

    /**
     * Horizontal layout direction is from Right to Left.
     */
    public static final int RTL = 1;



    public LinearLayout(Context context){
        super(context);
        mOrientation = 1;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;

        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;

        final int count = 2;//getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) {
            case Gravity.BOTTOM:
                // mTotalLength contains the padding already
                childTop = mPaddingTop + bottom - top - mTotalLength;
                break;

            // mTotalLength contains the padding already
            case Gravity.CENTER_VERTICAL:
                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                break;

            case Gravity.TOP:
            default:
                childTop = mPaddingTop;
                break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);

            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop , childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin;
        }
    }

    void layoutHorizontal(int left, int top, int right, int bottom) {
    }

    private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }


    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        public float weight;
        public int gravity = -1;

        public LayoutParams(LinearLayout.LayoutParams source) {
            super(source);

            this.weight = source.weight;
            this.gravity = source.gravity;
        }
    }

    public int getLayoutDirection() {
        return LTR;
    }


}

最后使用。

public class MainActivity extends AppCompatActivity {
    private ViewRootImpl mViewRoot;
    private LinearLayout mLinearLayout;
    private TextView mTextView1;
    private TextView mTextView2;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewRoot = new ViewRootImpl();
        mLinearLayout = new LinearLayout(this);
        mTextView1 = new TextView(this);
        mTextView2 = new TextView(this);

        mLinearLayout.addView(mTextView1,mTextView2);
        mViewRoot.setView(mLinearLayout);
        mViewRoot.doTraversal();
    }
}

ImageViewViewonLayout不会被重写(例如等)并且onLayout内容为空,而LinearLayoutViewGrouponLayout会被重写,用于定义ViewGroup下的child的坐标。
ViewViewGroup共用layoutlayout用于设置ViewViewGroup等View的坐标。

主要需要分析一下LinearLayout 的onLayout过程。
假设现布局文件如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:orientation="vertical"
    android:gravity="center_vertical">
    <TextView
        android:layout_gravity="center_horizontal"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        />

</LinearLayout>

mOrientation == VERTICAL,因此调用layoutVertical.

传过来的数据为left= 0,top = 0,right = 100,bottom = 100,mPaddingTop= 0,mPaddingRight = 10,mPaddingRight= 10那么width = 100,childRight = 90.childSpace = 80

mTotalLengthLinearLayout调用measure的时候这个值 会具体赋值 这里为子view们的总长度加上padding的值,某些情况下还有加上margin的值(例如LinearLayout设置大小为wrap_content ),这里由于定义了TextView的大小,并且LinearLayoutmPaddingTopmPaddingBottom为0,所以mTotalLength = 50.

由于LinearLayout 设置的gravity属性为Gravity.CENTER_VERTICAL,那么childTop = 0 + (100-0-50)/2 = 25
进入for循环后,走到case Gravity.CENTER_HORIZONTAL。测量后childWidth = 50 ,child 设置的leftMarginrightMargin为10,因此childLeft = 10 + (80-50)/2 + 10-10 = 25。
因此childLeft = 25 childTop = 25 childWidth = 50 childHeight = 50.
最后调用 child.layout(left, top, left + width, top + height);child.layout(25,25,75,75).
最终会调用View中的setFrame(l, t, r, b),将(25,25,75,75)存入mRenderNode。

调用performLayout是将ViewGroup和View的Left,Top,Right,Bottom四个数据存入RenderNode,设置RenderNode的 mDirtyPropertyFields。

具体是如何渲染的呢?这里面就又涉及到计算机图形学了。。这真是个绕不开的门槛。先略过,接下来是draw的流程。

参考链接:
自定义View原理篇(2)- layout过程
Android 4.3中的视觉边界布局(Optical bounds layout)
掩码
自定义 view - 布局 onLayout
Android 重学系列 View的绘制流程(六) 硬件渲染(上)
Android 系统架构 —— View 的硬件渲染
渲染基础-渲染管线(Render-pipeline)

LinearLayout——线性布局(下)

相关文章

网友评论

      本文标题:Android View的绘制简单分析三

      本文链接:https://www.haomeiwen.com/subject/bmoacktx.html