美文网首页
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