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

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

作者: 帝王鲨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