欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android自定义View之onLayout

程序员文章站 2022-06-08 17:41:40
...

onLayout


 写过自定义ViewGroup的都知道,当自定义一个类继承ViewGroup之后,必须要重写的一个方法就是onLayout。
那么onLayout有什么样的作用呢?为什么自定义ViewGroup就需要重写该方法,自定义View则不需要重写该方法?
疑问出在ViewGroup的onLayout里,那我们就从这里入手,逐一去分析各中原由。
 首先,进入到ViewGruop代码我们看到onLayout()是一个抽象方法,所以在子类实现中我们必须要重写该方法,并且是重写于父类的方法,
所以该方法的具体调用应该是在父类中。

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

 在ViewGroup中还有一个方法layout,是被final修饰的,说明该方法不能被子类重写。

public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

 这里主要关注它实际上只是调用了父类的layout方法,为了让代码看起来更加清晰,这里只贴出View的layout关键的代码。

public void layout(int l, int t, int r, int b) {
  ......
/*setFrame实际上就是根据四个坐标值设置了当前View本身的layout,所以View的layout已经自己设置好了自己的位置,
 *ViewGroup实际继承自View,通过调用父类的layout也完成了自身layout的设置。
*/
boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//接着我们看到,在layout内,在设置完自身的layout之后调用了onLayout,而onLayout就是在我们自定义ViewGroup时需要去实现的方法
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    onLayout(changed, l, t, r, b);
    }
    ......
}

 其实,onLayout就是ViewGroup用来设置内部子View的位置的方法,在onLayout方法内,需要依次遍历childView,并调用childView的layout(l, t, r, b)方法来为每个子View设置具体的布局位置,参数为左上右下四个坐标值。那么这四个坐标值如何设置呢?此处四个坐标位置理论上是可以随便设置的,但是为了达到对应的布局效果,通常是根据measure测量之后得到的View的尺寸来计算相应的坐标值。这里我通过模拟LinearLayout的横向布局和纵向布局的onLayout方法来看下具体怎么去写onLayout。
 (补充一点:onMeasure的作用:测量View的尺寸大小,为layout(View的布局位置)提供参考(这里用参考来描述是因为layout(l,t,r,b)
的几个坐标值是可以随便设置的,只是随便设置可能布局比较乱或者可能会出现重叠等等,所以一般还是根据布局需求,参考view的尺寸进行设置))

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

    private void layoutVertical(int l, int t, int r, int b) {
        int top = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            int width = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();
            //纵向位置均为从0到height,横向需要不断累加width(上一个子view的左下点为当前子view的左上顶点)
            childView.layout(0, top, width, top + height);
            top += height;
        }
    }

    private void layoutHorizontal(int l, int t, int r, int b) {
        int left = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            int width = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();
            //横向位置均为从0到width,纵向需要不断累加height(上一个子View的右上顶点为当前View的左上顶点)
            childView.layout(left, 0, left + width, height);
            left += width;
        }
    }

 在onLayout方法中,设置layout的四个坐标值(左上右下)时,为了达到需要的布局排列效果,通常是根据子View的尺寸来计算四个坐标值。
 在上面的例子中,可以看到,通过getMeasuredWidth()和getMeasuredHeight()来分别获取到了childView的寬高。在开发过程中,我们可能还会看到这样的两个方法getWidth()和getHeight(),这也是获得View寬高的方法,那么二者有什么区别吗?上面onLayout可以使用getWidth()和getHeight()来代替getMeasuredWidth()和getMeasuredHeight()吗?
 答案是不行!你可以试试看。


 下面我们就来看看getMeasuredWidth()、getMeasuredHeight()与getWidth()、getHeight()存在什么样的差别。
二话不说上源码,相信一看代码你就立马明白了。

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

 这里看过View的测量onMeasure方法原理的一看就能看明白此处是如何获取的寬高,这里简单说一下mMeasuredWidth、mMeasuredHeight两个值便是onMeasure方法设置最终的View的尺寸大小的方法setMeasuredDimension(int measuredWidth, int measuredHeight)传入的两个值。详细的onMeasure的原理可以移步自定义View之onMeasure原理解析
下面再看下getWidth()和getHeight()

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

 这一看left、right、top、bottom就能立马想到这就是layout方法传入的四个坐标值,不信你可以看看,它在setFrame(int left, int top, int right, int bottom)
里赋的值。而setFrame()不就是之前说过的设置自身布局位置的方法么。

小结:

  1. 看到这里应该明白了吧,getMeasuredWidth、getMeasuredHeight经过measure测量之后才能获取到的。
    而getWidth、getHeight是经过layout设置过view布局位置之后才能获取到。所以,在onLayout使用getWidth和getHeight是不能得到对应的寬高的。
  2. 通常情况下,getWidth、getHeight和getMeasureWidth、getMeasureHeight得到的值是一致的,但是layout的
    四个坐标点是可以随便设置的,不一定会根据measure测量的大小对应来设置,所以两种方式存在不等的情况。