Android View 布局 过程

来源:互联网 发布:网站模板源码免费下载 编辑:程序博客网 时间:2024/06/04 19:48

​ 当Android 的View测量完毕之后,才可以去布局。我们在测量时获取到的MeasuredHeightMeasuredWidth,在布局的时候就会用到,毕竟只有知道尺寸才好确定位置。

布局相对于测量来说就相对简单了很多。布局的过程就是确定View在界面显示位置的过程。View是一个矩形,决定一个View在界面显示的位置的信息主要是四个边到父View的边距,这里会有四个变量来记录各个边到父View的距离:

  • mLeft:View左边缘距离父View左部边缘的距离
  • mTop:View上边缘距离父View上部边缘的距离
  • mBottom:View底边缘距离父View上部边缘的距离
  • mRight:View右边缘距离父View左部边缘的距离

注意:这里的距离单位是像素;Android 屏幕的像素原点在屏幕的左上角,所以View的宽高计算如下:

View的宽=mRight-mLeft
View的高=mBottom-mTop

这里的宽高和测量来的MeasuredWidth和MeasuredHeight是不同的,不要混淆.看它们的源码:

测量得到的高

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

View实际的高

public final int getHeight() {    return mBottom - mTop;}

width和MeasuredWidth代码跟上面的代码类似就不在贴了。

布局是绘制的前提,只有布局完毕才可以去绘制。View布局时调用的方法是layout(int l, int t, int r, int b)它的是个参数就是代表View四个边距离父View的距离。虽然View了layout 方法,但是不提倡去重写它,如果有需要我们应该去重写 onLayout(boolean changed, int left, int top, int right, int bottom),我们先看一下layout方法:

public void layout(int l, int t, int r, int b) {    //判断onMeasure是否应该跳过,(变量mPrivateFlags3存着一些关于Layout的信息,应该是用来判断View是否测量过,具体的没研究过)    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;    //根据布局模式来判断是调用setOpticalFrame还是setFrame;    //setOpticalFrame内部也会再次调用setFrame,所以不论isLayoutModeOptical是false还是true,都会调用setFrame    //setFrame用来分配View的位置和大小,如果新传入的位置和尺寸与之前的位置和尺寸有差别就会返回true。    //所以这里获取的是View的位置或者尺寸是否发生变化    boolean changed = isLayoutModeOptical(mParent) ?            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {        //如果View的位置或者尺寸发生变化或者满足(mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED,就会首先调用onLayout        //onLayout是空的,没有任何实现,需要我们自己去实现。        //凡是继承ViewGroup的类一般都要重写这方法,来确定其中的子view的位置和尺寸        onLayout(changed, l, t, r, b);        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;        //这里设置 OnLayoutChangeListener 监听事件        //ListenerInfo里面专门有个ArrayList来存储OnLayoutChangeListener        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnLayoutChangeListeners != null) {            //对ArrayList中的OnLayoutChangeListener中的事件监听器进行克隆            ArrayList<OnLayoutChangeListener> listenersCopy =                   (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();            int numListeners = listenersCopy.size();            for (int i = 0; i < numListeners; ++i) {              //遍历添加进来的事件监听器,依次调用onLayoutChange方法;这样layout就能在收到布局发生变化时得到响应了                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);            }        }    }    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

onLayout(boolean changed, int left, int top, int right, int bottom)方法很简单,首先会判断一下是需要测量一下,如果不需要,记录一下传入的四个边到父View的边距;然后判断View的尺寸和位置是否变化过,如果变化去就调用onLayout方法,并且遍历OnLayoutChangedListener监听器。

onLayout(boolean changed, int left, int top, int right, int bottom)这个方法是空的,是需要子类去重写的。它的主要作用就是确定该View中子view的位置。通常会遍历该View中所有的子view,然后依次调用子view的layout方法,来确定子View的位置和尺寸。我们看一下FrameLayout中部分代码:

@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    layoutChildren(left, top, right, bottom, false /* no force left gravity */);}void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {    //获取子View的个数    final int count = getChildCount();    //获取父View的四边    final int parentLeft = getPaddingLeftWithForeground();    final int parentRight = right - left - getPaddingRightWithForeground();    final int parentTop = getPaddingTopWithForeground();    final int parentBottom = bottom - top - getPaddingBottomWithForeground();    //遍历所有的子view    for (int i = 0; i < count; i++) {        final View child = getChildAt(i);        //只有在子view可见并且不占据位置的时候才去设置子view的尺寸和位置        if (child.getVisibility() != GONE) {            //……省略子View在各个Gravity情况下四边距离屏幕边距的数值……            child.layout(childLeft, childTop, childLeft + width, childTop + height);        }    }}

所以我们在继承ViewGroup时,重写onLayout方法也可以做类似的处理。

​ 在上面我们提到布局变化监听,其实在View里提供了一个onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);当View的尺寸发生变化时就会调用这个方法。不过它刚加入的View树中时,它使用还之前的尺寸,都是0。

​ 在源码里,它是在 setFrame(int left, int top, int right, int bottom)中调用的。上面提到了setFrame是用来分配View的位置和大小,如果新传入的位置和尺寸与之前的位置和尺寸有差别就会返回true。简单看一下setFrame调用onSizeChanged的过程:

protected boolean setFrame(int left, int top, int right, int bottom) {    boolean changed = false;    //……省略log    //新传入边距跟之前记录的边距不同,就说明View的尺寸或者位置发生变化    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {        changed = true;        int drawn = mPrivateFlags & PFLAG_DRAWN;        int oldWidth = mRight - mLeft;        int oldHeight = mBottom - mTop;        int newWidth = right - left;        int newHeight = bottom - top;        //根据新边距和旧边距,判断尺寸是否发生变化        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);        //重新绘图        invalidate(sizeChanged);        //将新边距赋值        mLeft = left;        mTop = top;        mRight = right;        mBottom = bottom;        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);        mPrivateFlags |= PFLAG_HAS_BOUNDS;        //如果尺寸发生变化就会调用sizeChange,onSizeChanged就是在sizeChange方法里调用的        if (sizeChanged) {            sizeChange(newWidth, newHeight, oldWidth, oldHeight);        }        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {            mPrivateFlags |= PFLAG_DRAWN;            invalidate(sizeChanged);            invalidateParentCaches();        }        mPrivateFlags |= drawn;        mBackgroundSizeChanged = true;        if (mForegroundInfo != null) {            mForegroundInfo.mBoundsChanged = true;        }        notifySubtreeAccessibilityStateChangedIfNeeded();    }    return changed;}private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {    //调用onSizeChanged方法,这个方法是空的;如果有操作需要在尺寸发生变化时,可以重写这个方法    onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);    if (mOverlay != null) {      mOverlay.getOverlayView().setRight(newWidth);     mOverlay.getOverlayView().setBottom(newHeight);   }   rebuildOutline();}

上面代码没有什么难度,在setFrame方法里根据新边距和旧边距,判断尺寸是否发生变化,如果变化就去调用sizeChange方法,onSizeChanged就是在sizeChange中调用的。

0 0
原创粉丝点击