Android自定义View之onLayout

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测量的大小对应来设置,所以两种方式存在不等的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值