View框架之layout()流程

本文详细分析了Android中View的layout()流程,从performTraversals()开始,探讨了如何通过performLayout()和View的layout()方法确定View在屏幕中的位置。在layout()过程中,设置了view的四个顶点坐标,并在onLayout()方法中针对ViewGroup和View的不同情况进行布局处理。着重讨论了ViewGroup如RelativeLayout的onLayout()实现以及自定义View时重写onLayout()的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  在上一篇我们分析了view的measure()流程,当我们把view测量出来以后,接着就要算出这个view的在这个屏幕的具体位置,所以这一篇我们接着分析layout()流程,废话不多说,还是先来一张流程图,下面会根据这个流程图进行讲解

-

注意:RootView是一个FrameLayout,所以也是一个ViewGroup
这里写图片描述


  layout()过程和measure()过程一样,起点都是在ViewRootImpl中的performTraverals()方法,在performTraverals()中我们又调用了performLayout(),所以我们直接从performLayout()方法开始分析,先贴上performLayout()的源码:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
                 /**省略部分代码

                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                mHandlingLayoutInLayoutRequest = false;

                // Check the valid requests again, this time without checking/clearing the
                // layout flags, since requests happening during the second pass get noop'd
                validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                if (validLayoutRequesters != null) {
                    final ArrayList<View> finalRequesters = validLayoutRequesters;
                    // Post second-pass requests to the next frame
                    getRunQueue().post(new Runnable() {
                        @Override
                        public void run() {
                            int numValidRequests = finalRequesters.size();
                            for (int i = 0; i < numValidRequests; ++i) {
                                final View view = finalRequesters.get(i);
                                Log.w("View", "requestLayout() improperly called by " + view +
                                        " during second layout pass: posting in next frame");
                                view.requestLayout();
                            }
                        }
                    });
                }
            }

        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}  
-
-
  这里的的host就是我们的根布局DecorView,因为DecorView是一个FrameLayout,而layout()是view中被修饰final的方法,所以我们这里调用的是View的layout的方法,layout的四个参数代表view的四个顶点的位置,默认的DecorView左边距屏幕的距离和上边距屏幕的边距都是0,接着贴下View的layout方法
  @SuppressWarnings({"unchecked"})
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;
      /**
      *   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);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
-
-
  当执行setFrame()方法后,会直接初始化mLeft , mTop ,mRight,mBottom这几个值,因为我们一般在view中调用 getLeft() getTop() getRight() getBottom()获取的就是这几个值,所以我们一般必须得等layout()结束以后才能获取到这几个值
   protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    /** 省略部分代码
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
     /** 省略部分代码
}

-
-
  接着继续执行我们的onLayout()方法,onLayout()方法是view中的方法,在view中是一个默认空实现,在viewGroup是一个抽象方法(继承viewGroup必须实现,因为要对子view进行布局),所以我们分两种情况来介绍
  ViewGroup的onLayout(),以RelativeLayout为例
 @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    //  The layout has actually already been performed and the positions
    //  cached.  Apply the cached values to the children.
    //遍历子view
    final int count = getChildCount();

    for (int i = 0; i < count; i++) {
        View childView = getChildAt(i);
        if (child.getVisibility() != GONE) {
            RelativeLayout.LayoutParams st =
                    (RelativeLayout.LayoutParams) child.getLayoutParams();
                    //调用每个childView的layout()方法,继续走上面的layout方法
            childView.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
        }
    }

-

View的onLayout(),默认是一个空实现,一般情况下我们自定义控件的时候,会去重写它,实现自己的逻辑。
  view中的onLayout():

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

-

-

好了,差不多就这么多了,到了这一步基本确定了view的具体位置,接着就是绘制了…..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值