博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义View onLayout篇
阅读量:5812 次
发布时间:2019-06-18

本文共 11897 字,大约阅读时间需要 39 分钟。

OK,先提一下完结的onMeasure 篇,没看完的小伙伴先看一下onMeasure 先说一下View的layout 和 onLayout。 这里为了方便理解,以写出自定义View为目的,不做太深入,其一是因为,我们知道这么多,就已经可以写出自定义ViewGroup了,另一方面,深入了我也不知道。总之,大家在看完文章,如果想知道更多的细节的话,就去研究一下View的layout源码。

OK,话不多说,先分析layout主要源码

先看一下View layout方法的源码

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;        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);            if (shouldDrawRoundScrollbar()) {                if(mRoundScrollbarRenderer == null) {                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);                }            } else {                mRoundScrollbarRenderer = null;            }            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                ArrayList
listenersCopy = (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; }复制代码

按惯例先说一下每个参数 l:View左边界距离父容器的左边界的距离 t:View上边界距离父容器上边界的距离 r:View右边界距离父容器左边界的距离 b:View下边界距离父容器上边界的距离

具体如下图所示如下图所示:(图片是老师[GcsSloop]Github上面的 我拷贝来用一下)

好的,下面可以直接看几组关键代码

boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);复制代码

可以看到,isLayoutModeOptical这个方法,是判断是否有光学边界的(光学边界这里暂时用不到,请自行谷歌)我们来仔细看setOpticalFrame,setFrame这两个方法

private boolean setOpticalFrame(int left, int top, int right, int bottom) {        Insets parentInsets = mParent instanceof View ?                ((View) mParent).getOpticalInsets() : Insets.NONE;        Insets childInsets = getOpticalInsets();        return setFrame(                left   + parentInsets.left - childInsets.left,                top    + parentInsets.top  - childInsets.top,                right  + parentInsets.left + childInsets.right,                bottom + parentInsets.top  + childInsets.bottom);    }复制代码

可以看到,这个setOpticalFrame方法,最终也是调用了setFrame,那好我们可以直接继续看setFrame方法了。

protected boolean setFrame(int left, int top, int right, int bottom) {        boolean changed = false;        if (DBG) {            Log.d("View", this + " View.setFrame(" + left + "," + top + ","                    + right + "," + bottom + ")");        }        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {            changed = true;            // Remember our drawn bit            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 our old position            invalidate(sizeChanged);            mLeft = left;            mTop = top;            mRight = right;            mBottom = bottom;            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);            mPrivateFlags |= PFLAG_HAS_BOUNDS;            if (sizeChanged) {                sizeChange(newWidth, newHeight, oldWidth, oldHeight);            }            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {                // If we are visible, force the DRAWN bit to on so that                // this invalidate will go through (at least to our parent).                // This is because someone may have invalidated this view                // before this call to setFrame came in, thereby clearing                // the DRAWN bit.                mPrivateFlags |= PFLAG_DRAWN;                invalidate(sizeChanged);                // parent display list may need to be recreated based on a change in the bounds                // of any child                invalidateParentCaches();            }            // Reset drawn bit to original value (invalidate turns it off)            mPrivateFlags |= drawn;            mBackgroundSizeChanged = true;            if (mForegroundInfo != null) {                mForegroundInfo.mBoundsChanged = true;            }            notifySubtreeAccessibilityStateChangedIfNeeded();        }        return changed;    }复制代码

这里面代码就不用一句一句的分析,看大概,我们便可以看出步骤,先是比较了新位置和老位置是否有差异,如果有差异会调用sizechanged来更新我们View的位置。

OK 这个方法大概分析完毕了,我们先回到layout方法继续 onLayout(changed, l, t, r, b); OK ,找到今天的主角了。我们点进去这个方法,看里面做了什么。

/**     * Called from layout when this view should     * assign a size and position to each of its children.     *     * Derived classes with children should override     * this method and call layout on each of     * their children.     * @param changed This is a new size or position for this view     * @param left Left position, relative to parent     * @param top Top position, relative to parent     * @param right Right position, relative to parent     * @param bottom Bottom position, relative to parent     */    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }复制代码

有点奇怪,什么也没做。这个其实是android留给我们自己去实现的一个方法,也就是大家都知道的,去布局子View的位置,只有含有子View的容器,才需要重写这个方法,也就是ViewGroup。

OK ,通过上面的分析,可以得到两个结论 1、View通过layout方法来确认自己在父容器中的位置 2、 ViewGroup通过onLayout 方法来确定View在容器中的位置

OK,光有理论没什么卵用,来实现一个简单的流式布局,来验证一下

public class MyViewGroup extends ViewGroup {    public MyViewGroup(Context context) {        super(context);    }    public MyViewGroup(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);       //遍历子View,测量每个View的大小        for (int i = 0; i < getChildCount(); i++) {            View view = getChildAt(i);            measureChild(view, widthMeasureSpec, heightMeasureSpec);        }    }}复制代码

自定义ViewGroup,通过onLayout()方法给子View布局,前提,我们必须得知道每个子View的宽度和高度,对吧。所以我们先要在onMeasure的时候,测量一下每个子View的具体大小,前面已经把View和ViewGroup的onMeasure都分析过了,这边不在赘述。直接遍历子View,然后measureChild即可得到所有子View的measureSize(注意这里说的是measureSize,为什么是measureSize,之后再谈)。

OK 已经测量出子View的具体大小了,那么下面,我们就来安排他们的位置。

private int horizontalSpace = 10;//水平间距    private int verticalSpace = 10;//垂直间距    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int hadUsedHorizontal = 0;//水平已经使用的距离        int hadUsedVertical = 0;//垂直已经使用的距离        int width = getMeasuredWidth();//        int height = getMeasuredHeight();        for (int i = 0; i < getChildCount(); i++) {            View view = getChildAt(i);            //判断是否已经超出宽度            if (view.getMeasuredWidth() + hadUsedHorizontal > width) {                //已经超出了宽度                hadUsedVertical = hadUsedVertical + view.getMeasuredHeight() + verticalSpace;                hadUsedHorizontal = 0;            }            view.layout(hadUsedHorizontal, hadUsedVertical, hadUsedHorizontal + view.getMeasuredWidth(), hadUsedVertical + view.getMeasuredHeight());            hadUsedHorizontal = hadUsedHorizontal + horizontalSpace + view.getMeasuredWidth();        }    }复制代码

我们先是定义了水平已经使用的距离,和垂直已经使用的距离,而且,如果有需要 我们还需要水平和垂直的间距,都定义出来。OK,可以看到,逻辑很简单,每次layout子View的时候,我们都要判断,子View宽度,已经超出了父View的宽度,如果超出了,就换行。最后调用子View的layout来确定子view的位置。 OK,记得刚才说我们源码里面获取子View大小的时候,宽度为例子使用getMeasuredWidth,为什么用这个而不用getWidth呢?也就是我前面说的measureSize

OK我们看一下。我们在ViewGroup onMeasure的时候,调用了measureChild方法。我们看一下这个源码

protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }复制代码

调用了child.measure,也就是view的measure方法。我们继续看里面做了什么,由于代码很多,我就不粘贴了。view的.measure 方法调用了自己的onMeasure方法,也就像我们在onMeasure说的那样,之后子View会调用setMeasuredDimension来提交自己的宽高。我们看看这个setMeasuredDimension

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }复制代码

OK ,这就清楚了,最终,我们调用measureChild方法 最终会把子View的大小传给mMeasuredSize。那可能会有朋友问,那getWidth,和getHeight会得到什么呢?在onMeasure 方法的时候,getwidth 和getheight都是0;为什么呢? View的getWidth源码:

public final int getWidth() {        return mRight - mLeft;    }复制代码

而我们刚才分析layout的源码时候就知道,mRight和mleft是在layout方法之后才赋值的,所以在测量子View的时候,是无法拿到getWidth 和 getHeight的。 OK 最后贴出源码和布局文件

public class MyViewGroup extends ViewGroup {    private int horizontalSpace = 10;    private int verticalSpace = 10;    public MyViewGroup(Context context) {        super(context);    }    public MyViewGroup(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int hadUsedHorizontal = 0;//水平已经使用的距离        int hadUsedVertical = 0;//垂直已经使用的距离        int width = getMeasuredWidth();//        int height = getMeasuredHeight();        for (int i = 0; i < getChildCount(); i++) {            View view = getChildAt(i);            //判断是否已经超出宽度            if (view.getMeasuredWidth() + hadUsedHorizontal > width) {                //已经超出了宽度                hadUsedVertical = hadUsedVertical + view.getMeasuredHeight() + verticalSpace;                hadUsedHorizontal = 0;            }            view.layout(hadUsedHorizontal, hadUsedVertical, hadUsedHorizontal + view.getMeasuredWidth(), hadUsedVertical + view.getMeasuredHeight());            hadUsedHorizontal = hadUsedHorizontal + horizontalSpace + view.getMeasuredWidth();        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        for (int i = 0; i < getChildCount(); i++) {            View view = getChildAt(i);            measureChild(view, widthMeasureSpec, heightMeasureSpec);        }    }    /**     * @param child                   子View     * @param parentWidthMeasureSpec  宽度测量规格     * @param widthUsed               父view在宽度上已经使用的距离     * @param parentHeightMeasureSpec 高度测量规格     * @param heightUsed              父view在高度上已经使用的距离     */    @Override    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {        super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);    }}复制代码

布局文件

复制代码

最终效果

之后我们还会继续来完善这个ViewGroup,让他变成一个强大的ViewGroup。OK,onLayout可能就写到这里了,如果有补充的,之后会在补充。这个分析的比较简单,为了是让新人能快速的学会如何使用onLayout,如果大家想深入了解,建议去谷歌一下onLayout,有很多讲的比较详细的。 那么我们明天见喽。 当然,如果你喜欢,别忘了赞赞赞赞我。

转载地址:http://iitbx.baihongyu.com/

你可能感兴趣的文章
文件查找
查看>>
shell编程前言(一)
查看>>
5、centos7.*配置yum的EPEL源及其它源
查看>>
JSON前后台简单操作
查看>>
shell中一些常见的文件操作符
查看>>
CentOS 7 装vim遇到的问题和解决方法
查看>>
JavaScript基础教程1-20160612
查看>>
使用第三方类、库需要注意的正则类RegexKitLite的使用
查看>>
iOS \U7ea2 乱码 转换
查看>>
FCN图像分割
查看>>
ios xmpp demo
查看>>
python matplotlib 中文显示参数设置
查看>>
数据库事务隔离级别
查看>>
os模块大全详情
查看>>
【ros】Create a ROS package:package dependencies报错
查看>>
从内积的观点来看线性方程组
查看>>
kali linux 更新问题
查看>>
HDU1576 A/B【扩展欧几里得算法】
查看>>
廖雪峰javascript教程学习记录
查看>>
WebApi系列~目录
查看>>