Android N代码分析:requestLayout

 

 

1.1.1        requestLayout

下面的文章对于requestLayout的基本流程讲解得比较清楚,本文结合最新的代码做进一步的细化分析:

http://www.xuebuyuan.com/2134865.html

 

 

1)

当一个View调用requestLayout的时候,会给当前的View设置一个FORCE_LAYOUT标记。由此向ViewParent请求布局。这样从这个View开始向上一直requestLayout。最终到达ViewRootImpl。ViewParent 就是当前的传输链。【参见职责链设计模式】

   

 

     public void requestLayout() {

        if(mMeasureCache != null) mMeasureCache.clear();

 

        if(mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {

            //Only trigger request-during-layout logic if this is the view requesting it,

            //not the views in its parent hierarchy

           ViewRootImpl viewRoot = getViewRootImpl();

            if(viewRoot != null && viewRoot.isInLayout()) {

               if (!viewRoot.requestLayoutDuringLayout(this)) {

                   return;

                }

            }

           mAttachInfo.mViewRequestingLayout = this;

        }

 

        mPrivateFlags |=PFLAG_FORCE_LAYOUT;

        mPrivateFlags|= PFLAG_INVALIDATED;

 

        if(mParent != null && !mParent.isLayoutRequested()) {

            mParent.requestLayout();

        }

        if(mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {

           mAttachInfo.mViewRequestingLayout = null;

        }

    }

 

[ 注] mParent

  protectedViewParent mParent; 这是一个接口,由public final classViewRootImpl implements ViewParent,实现接口。

mParent的赋值在:

 View.java(frameworks\base\core\java\android\view):   void assignParent(ViewParent parent) {

ViewGroup.java(frameworks\base\core\java\android\view):            child.assignParent(this);

ViewRootImpl.java(frameworks\base\core\java\android\view):                view.assignParent(this);

ViewRootImpl.java(frameworks\base\core\java\android\view):       mView.assignParent(null);

WindowManagerGlobal.java(frameworks\base\core\java\android\view):            view.assignParent(null);

主要由ViewGroup. addViewInner进行赋值,即在添加View的时候将父View作为Parent赋值给childView,

     private void addViewInner(View child, intindex, LayoutParams params,

           boolean preventRequestLayout) {

 

        // tellour children

        if(preventRequestLayout) {

           child.assignParent(this);

        } else {

           child.mParent = this;

        }

 

public abstract class ViewGroup extends View implements ViewParent,ViewManager,可以看出VG是支持该类型的,可以对其赋值。虽然VG本身没有实现requestLayout(),但View实现了requestLayout(),但View却没有implements ViewParent,真是奇怪的关系和使用方法。

这种关系的结果,就是一直向上找根View,并requestLayout,根View就要分析DecorView的加载过程了。见其他章节。

 

    2)

    ViewRootImpl发现请求了布局。那么就会调用measure方法。

    measure方法确认当前View是否有FORCE_LAYOUT标记。

     如果有,那么就会进行重新measure。并且设置标记LAYOUT_REQUIRED。

 

    public finalvoid measure(int widthMeasureSpec, int heightMeasureSpec) {

        boolean optical = isLayoutModeOptical(this);

        if(forceLayout || needsLayout) {

            //first clears the measured dimension flag

           mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

 

           resolveRtlPropertiesIfNeeded();

 

            int cacheIndex = forceLayout ? -1 :mMeasureCache.indexOfKey(key);

            if(cacheIndex < 0 || sIgnoreMeasureCache) {

               // measure ourselves, this should set the measured dimension flag back

               onMeasure(widthMeasureSpec, heightMeasureSpec);

               mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

            }else {

               long value = mMeasureCache.valueAt(cacheIndex);

               // Casting a long to int drops the high 32 bits, no mask needed

               setMeasuredDimensionRaw((int) (value >> 32), (int) value);

               mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

            }

}

 

Android N相对于M,在onMeasure()部分做了优化,增加了判断条件,避免不需要的测量,同时会引起一些在N之前的不规范代码出现界面上的显示问题,如控件看不见,位置偏移等,这类问题我解决过多个。


 Android N代码分析:requestLayout_第1张图片

 

【注】这里还有一个比较明显的问题,ViewRootImpl是如何发现布局请求的,如前,VG执行的是View的requestLayout,那么ViewRootImpl的requestLayout是在这个条件下是如何触发的呢?(后续分析)


3)

 在随后的layout方法中,会判断这个标记。如果这个标记为true。

那么就一定会调用onLayout.

onLayout调用后清理LAYOUT_REQUIRED标记。

layout调用之后,会清理掉FORCE_LAYOUT标记。

 

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

        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;

 

        booleanchanged = 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) {

                ArrayListlistenersCopy =

                       (ArrayList)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;

    }

 

当然在上述过程中,影响到了兄弟或者是父亲View的大小, 那么也兄弟或者是父亲View也会调用layout/onLayout。不管其是否已经调用requestLayout。如果说指定的MeasureSpec为此也发生了变化,那么measure/onMeasure也会被调用。

 

通过上述分析发现,只要调用了requestlayout, 那么measure和onMeasure,以及layout,onlayout,draw onDraw都会被调用。

 

 

在很多情况下,requestLayout是不需要被调用的。例如,我们把一个AbsoluteLayout里面的childView挪动一下位置。我们仅仅需要调用的可能就是重新布局当前AbsoluteLayout,然后调用invalidate方法进行重绘。而不是从当前View向上的整个View树形结构都要重新layout,onLayout,measure,onMeasure一次。

这个时候,怎么办?

 

一种方法是,直接调用onLayout。然后调用invalidate进行重绘。很明显可以提升绘制效率。由于父View的layout实现中对会通知布局的listener。但是由于无法得到listener,因此调用onlayout的时候无法对其进行通知,这也是这种实现的缺陷。

 

你可能感兴趣的:(Android)