美文网首页
如何成为自定义高手(三)布局

如何成为自定义高手(三)布局

作者: 帝王鲨kingcp | 来源:发表于2019-01-22 20:34 被阅读0次

    布局过程

    • 确定每个View的位置和尺寸
    • 作用:为绘制和触摸范围做支持

    流程

    • 运行前,开发者在xml文件里写入对View的布局要求layout_xxx
    • 父View在自己的onMeasure()中,根据开发者在xml中写的对子View的要求,和自己的可用空间,得出对子View的位置和尺寸传给子View,子View保存。
    • 子View在自己的onMeasure()中,根据自己的特性算出自己的期望尺寸。
      如果是ViewGroup,还会在这里调用每个子View的实际尺寸和位置。
    • 父View在子View计算出期望尺寸后,得出子View的实际尺寸和位置
    • 子View在自己的layout方法中,将父View传进来的自己的实际尺寸和位置保存。
      如果是ViewGroup,还会在onLayout()里调用每个子View的layout()把它们是尺寸位置传给它们。

    具体开发

    继承已有的View,简单改写它们的尺寸:
    • 重写onMeasure()
    • 用getMeasureWidth()和getMeasureHeight()获取到测量出的尺寸
    • 计算出最终要的尺寸
    • 用setMeasuredDimension(width,height)把结果保存
    对自定义View完全进行自定义尺寸计算:
    • 重写onMeasure()。
    • 计算出自己的尺寸。
    • 用resolveSize()或者resolveSizeAndState()修正结果。
    • 使用setMeasuredDimension(width,height)保存结果。
      调用resolveSizeAndState()。
        /**
         * Version of {@link #resolveSizeAndState(int, int, int)}
         * returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
         */
        public static int resolveSize(int size, int measureSpec) {
            return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
        }
    

    specMode,specSize是父View 对子View的尺寸限制类型和具体限制尺寸。
    父布局如果是AT_MOST(表示父View对子View做出了尺寸上限): 会有两种情况:1.父布局可用空间specSize小于子View,这时给子view的空间只能是spaceSize,并且标记子view被压缩。2. 子View就是自己计算出的尺寸size
    父布局如果是EXACTLY(表示父View对子View的尺寸做出了精确限制): 子view就是父view的可用空间。
    父布局如果是UNSPECIFIED(表示父View对子View没有任何限制):其他情况都是子View计算出的尺寸。

        /**
         * Utility to reconcile a desired size and state, with constraints imposed
         * by a MeasureSpec. Will take the desired size, unless a different size
         * is imposed by the constraints. The returned value is a compound integer,
         * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
         * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
         * resulting size is smaller than the size the view wants to be.
         *
         * @param size How big the view wants to be.
         * @param measureSpec Constraints imposed by the parent.
         * @param childMeasuredState Size information bit mask for the view's
         *                           children.
         * @return Size information bit mask as defined by
         *         {@link #MEASURED_SIZE_MASK} and
         *         {@link #MEASURED_STATE_TOO_SMALL}.
         */
        public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
            final int specMode = MeasureSpec.getMode(measureSpec);
            final int specSize = MeasureSpec.getSize(measureSpec);
            final int result;
            switch (specMode) {
                case MeasureSpec.AT_MOST:
                    if (specSize < size) {
                        result = specSize | MEASURED_STATE_TOO_SMALL;
                    } else {
                        result = size;
                    }
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    break;
                case MeasureSpec.UNSPECIFIED:
                default:
                    result = size;
            }
            return result | (childMeasuredState & MEASURED_STATE_MASK);
        }
    
    自定义Layout
    • 重写onMeasure()
      1. 遍历每个子View,用measureChildWidthMargins()测量子View。
        需要重写generateLayoutParams()并返回MarginLayoutParams才能使用measureChildWidthMargins()方法。
        有些子View可能需要重新测量
        测量完成后,得出子View的实际位置和尺寸,并暂时保存

    解析一下measureChildWidthMargins()方法。第一行代码解释为什么需要重写generateLayoutParams()。调用getChildMeasureSpec方法,获得子View的MeasureSpec,最后child.measure(childWidthMeasureSpec, childHeightMeasureSpec),子View完成自我测量。重点看一下getChildMeasureSpec方法。

        /**
         * Ask one of the children of this view to measure itself, taking into
         * account both the MeasureSpec requirements for this view and its padding
         * and margins. The child must have MarginLayoutParams The heavy lifting is
         * done in getChildMeasureSpec.
         *
         * @param child The child to measure
         * @param parentWidthMeasureSpec The width requirements for this view
         * @param widthUsed Extra space that has been used up by the parent
         *        horizontally (possibly by other children of the parent)
         * @param parentHeightMeasureSpec The height requirements for this view
         * @param heightUsed Extra space that has been used up by the parent
         *        vertically (possibly by other children of the parent)
         */
        protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    getChildMeasureSpec()方法,结合开发者设置的LayoutParams中的width和height与父View的剩余可用空间,得出子View的尺寸限制。并使用MeasureSpec.makeMeasureSpec()来返回结果。
    size为父View可用空间(父布局的总空间减去已用空间)。
    childDimension就是我们在xml中填写的layout_width中的数据。这就是开发者,自己认为需要的尺寸。

    1. 当childDimension为具体的值:无论什么情况resultSize=childDimension,resultMode=MeasureSpec.EXACTLY。及时父View的可用空间没有这么大,也要尊重开发者,让开发者自己觉得是否需要修改父控件。
    2. 当childDimension为MATCH_PARENT(需要填满父控件):当父View的specMode=MeasureSpec.EXACTLY(给子View确切值),resultSize=size,resultMode = MeasureSpec.EXACTLY;当父View的specMode=MeasureSpec.AT_MOST(给子View一个上限值),resultSize=size,resultMode = MeasureSpec.AT_MOST;当父View的specMode=MeasureSpec.UNSPECIFIED(子View想要多大就多大),那么子View填满父控件就无从谈起,因此就用方案:resultSize=0,resultMode = MeasureSpec.UNSPECIFIED。
    3. 当childDimension为WRAP_CONTENT(即要求在不超过限制尺寸的情况下,自己选择合适的尺寸):那么当父View的specMode=MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY都是有限制的,resultSize=size,resultMode = MeasureSpec.AT_MOST;当父View的specMode=MeasureSpec.UNSPECIFIED(子View想要多大就多大),同样的道理没有具体上限,resultSize=0,resultMode = MeasureSpec.UNSPECIFIED。
        /**
         * Does the hard part of measureChildren: figuring out the MeasureSpec to
         * pass to a particular child. This method figures out the right MeasureSpec
         * for one dimension (height or width) of one child view.
         *
         * The goal is to combine information from our MeasureSpec with the
         * LayoutParams of the child to get the best possible results. For example,
         * if the this view knows its size (because its MeasureSpec has a mode of
         * EXACTLY), and the child has indicated in its LayoutParams that it wants
         * to be the same size as the parent, the parent should ask the child to
         * layout given an exact size.
         *
         * @param spec The requirements for this view
         * @param padding The padding of this view for the current dimension and
         *        margins, if applicable
         * @param childDimension How big the child wants to be in the current
         *        dimension
         * @return a MeasureSpec integer for the child
         */
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    
    • 重写onLayout()
      遍历每个子View,调用它们的layout()方法来将位置和尺寸传给它们。

    如何成为自定义高手(一)绘制
    如何成为自定义高手(二)动画
    如何成为自定义高手(三)布局
    如何成为自定义高手(四)触摸反馈,事件分发机制
    如何成为自定义高手(五)多点触摸
    如何成为自定义高手(六)滑动和拖拽
    如何成为自定义高手(七)滑动冲突

    相关文章

      网友评论

          本文标题:如何成为自定义高手(三)布局

          本文链接:https://www.haomeiwen.com/subject/wfmnxqtx.html