文章目录

      • `View`的 `onMeasure`
        • `onMeasure`要关注那些内容:
          • 子控件的宽高能大于父控件吗?
          • 正常的处理逻辑
      • `ViewGroup` 的 `onMeasure`
          • `measureChild` 与 `measureChildWithMargins`的区别

ViewonMeasure

先看普通 View的测量方法

onMeasure要关注那些内容:

子控件的宽高能大于父控件吗?
<LinearLayout        android:layout_width="400dp"        android:layout_height="wrap_content"        android:background="#3333"        android:orientation="vertical">        <com.python.cat.potato.view.custom.ItemView            android:layout_width="600dp"            android:layout_height="wrap_content"            android:background="#600f" />        <TextView            android:id="@+id/tv_test"            android:layout_width="700dp"            android:layout_height="80dp"            android:text="@string/task_count" />    LinearLayout>
View testView = view.findViewById(R.id.tv_test);testView.post(() -> {    LogUtils.w("700dp==" + SizeUtils.dp2px(700));    LogUtils.w("testView " + testView.getWidth() + " , " + testView.getHeight());    ViewGroup parent = (ViewGroup) testView.getParent();    LogUtils.w("testView.parent " + parent.getWidth() + " , " + parent.getHeight());});

输出log:

2018-12-19 23:51:28.699 7096-7096/ W/LogUtils:  700dp==18372018-12-19 23:51:28.700 7096-7096/ W/LogUtils : testView 1838 , 2102018-12-19 23:51:28.700 7096-7096/W/LogUtils: testView.parent1050 , 236

通过日志可以看到系统控件并没有对这种情况进行处理,那么,自定义控件也就不用处理这种情况。

正常的处理逻辑
  • MeasureSpec.AT_MOST的情况下,定义一个最小值,可以直接是0,或者是一个自定义的最小值,比如10dp.
  • 在其他情况下,使用父控件建议的宽高。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);      int fw = resolveMeasure(widthMeasureSpec, MIN_WIDTH);    int fh = resolveMeasure(heightMeasureSpec, MIN_HEIGHT);    LogUtils.d("fw=" + fw + " , " + fh);    setMeasuredDimension(fw, fh);}private int resolveMeasure(int lenMeasureSpec, int minLength) {    int len;    int mode = MeasureSpec.getMode(lenMeasureSpec);    int size = MeasureSpec.getSize(lenMeasureSpec);    switch (mode) {        case MeasureSpec.AT_MOST:            len = minLength;            break;        default:            len = size;            break;    }    return len;}

在不重写 onLayout() 的前提下 ,setMeasuredDimension(fw, fh); 里面设置的大小,就是当前控件的尺寸大小了。

ViewGrouponMeasure

对于容器而言,宽高往往跟里面的item大小有关,而且,要考虑对item设置margin的支持。

measureChildmeasureChildWithMargins的区别
  • 简单来说,就是一句话: measureChildWithMargins支持给child设置 margin,而measureChild 不支持。

  • 细分的话,通过这两个方法测量的child,在调用getMeasuredWidth/Height的时候,获取的值是不一样的(前提是给child设置了margin)。

// 二者之间的关系可以用一行表达式来说明:int measuredChildWidth = child.lp.marginLeft + child.lp.marginRight + measureChildWithMarginsWidth;

这两个方法都是ViewGroup.java里面的实现方法。其实源码很短,可以直接看一下。

    /**     * Ask one of the children of this view to measure itself, taking into     * account both the MeasureSpec requirements for this view and its padding.     * The heavy lifting is done in getChildMeasureSpec.     *     * @param child The child to measure     * @param parentWidthMeasureSpec The width requirements for this view     * @param parentHeightMeasureSpec The height requirements for this view     */    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);    }    /**     * 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);    }

只看宽度,高度同理。对于宽度,

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);  // 这是 measureChild() 里面的宽度
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                        + widthUsed, lp.width);  // 这是 measureChildWithMargins() 里面的宽度设置

可以看出,对于 带margin 的测量方法,比起不带margin而言,多了
+ lp.leftMargin + lp.rightMargin + widthUsed 这样的一个数值。那么,这里的 widthUsed 应该写多少呢?

我一开始也不知道这里应该写多少,但是对比一下上面的方法就知道了,下面这个方法的目的是支持child设置margin ,那么,widthUsed这个变量就没什么作用了,直接设置为0即可。

但是这样说似乎也没有很强的说服力,看一下很多系统自带的容器控件,在使用这个方法的时候,给widthUsed赋值为多少。几乎全部是0,heightUsed也是一样,全部是0。

比如FrameLayout,ActionBarView等。

好,那对于自定义的ViewGrouponMeasure 就好办了。

比如,我们自定义一个线性布局,竖直排列的。那么,onMeasure就可以这样写:

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int ws = MeasureSpec.getSize(widthMeasureSpec);        int wm = MeasureSpec.getMode(widthMeasureSpec);        int hs = MeasureSpec.getSize(heightMeasureSpec);        int hm = MeasureSpec.getMode(heightMeasureSpec);        int childCount = getChildCount();        int width = 0;        int height = 0;        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//            measureChild(child, widthMeasureSpec, heightMeasureSpec);            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);            int measuredWidth = child.getMeasuredWidth();            int measuredHeight = child.getMeasuredHeight();            height += measuredHeight + lp.topMargin + lp.bottomMargin;            width = measuredWidth + lp.leftMargin + lp.rightMargin;            width = Math.min(width, ws);        }        int finalW = wm == MeasureSpec.EXACTLY ? ws : width;        int finalH = hm == MeasureSpec.EXACTLY ? hs : height;        com.apkfuns.logutils.LogUtils.i("measure: " + finalW + " , " + finalH);        setMeasuredDimension(finalW, finalH);    }

特别注意,这里面测量 child 的尺寸的目的还是为了设置自己的尺寸。

然后,既然测量的时候需要考虑 margin,那么 布局的时候,也是一样。

    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        com.apkfuns.logutils.LogUtils.d(String.format("%s -- %s , %s , %s , %s", changed, l, t, r, b));        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//            LogUtils.d(lp);            l = lp.leftMargin;            t = t + lp.topMargin;            r = l + child.getMeasuredWidth();            b = t + child.getMeasuredHeight();            child.layout(l, t, r, b);            t += child.getMeasuredHeight() + lp.bottomMargin;//            break;            com.apkfuns.logutils.LogUtils.d("item width:" + (lp.rightMargin + lp.leftMargin + child.getMeasuredWidth()));        }    }

这样,就在布局 child的时候,设置上了对应的margin

注意:以上只是实现了对margin的支持,至于 padding并没有。

完成代码(添加了对自身 padding 的支持):

public class LineLayout extends ViewGroup {    public LineLayout(Context context) {        this(context, null);    }    public LineLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public LineLayout(Context context, AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr, 0);    }    public LineLayout(Context context, AttributeSet attrs,                      int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        LogUtils.d("size w=" + w + " ,,, h=" + h);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int ws = MeasureSpec.getSize(widthMeasureSpec);        int wm = MeasureSpec.getMode(widthMeasureSpec);        int hs = MeasureSpec.getSize(heightMeasureSpec);        int hm = MeasureSpec.getMode(heightMeasureSpec);        int childCount = getChildCount();        int width = 0;        int height = 0;        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//            measureChild(child, widthMeasureSpec, heightMeasureSpec);            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);            int measuredWidth = child.getMeasuredWidth();            int measuredHeight = child.getMeasuredHeight();            height += measuredHeight + lp.topMargin + lp.bottomMargin;            width = measuredWidth + lp.leftMargin + lp.rightMargin;            width = Math.min(width, ws);        }        int finalW = wm == MeasureSpec.EXACTLY ? ws : width + getPaddingLeft() + getPaddingRight();        int finalH = hm == MeasureSpec.EXACTLY ? hs : height + getPaddingTop() + getPaddingBottom();        com.apkfuns.logutils.LogUtils.i("measure: " + finalW + " , " + finalH);        setMeasuredDimension(finalW, finalH);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        com.apkfuns.logutils.LogUtils.d(String.format("%s -- %s , %s , %s , %s", changed, l, t, r, b));        t = getPaddingTop();        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//            LogUtils.d(lp);            l = lp.leftMargin + getPaddingLeft();            t = t + lp.topMargin;            r = l + child.getMeasuredWidth();            b = t + child.getMeasuredHeight();            child.layout(l, t, r, b);            t += child.getMeasuredHeight() + lp.bottomMargin;//            break;            com.apkfuns.logutils.LogUtils.d("item width:" + (lp.rightMargin + lp.leftMargin + child.getMeasuredWidth()));        }    }    @Override    protected MarginLayoutParams generateDefaultLayoutParams() {        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT);    }    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(), attrs);    }    @Override    protected LayoutParams generateLayoutParams(LayoutParams p) {        return new MarginLayoutParams(p);    }}

更多相关文章

  1. android POST请求(https)遇到的问题
  2. Android(安卓)第十一天重置版_新特性
  3. 解决WebView加载Https无法显示的问题
  4. android material design widget recyclerview
  5. android设置默认程序
  6. Android中父子窗体调用类似模式窗体应用
  7. Android(安卓)ListView拖动时,背景颜色会变成黑色
  8. Android(安卓)studio 3.0安装配置方法图文教程
  9. Android开发学习 之 五、基本界面控件

随机推荐

  1. 系统控制—清除历史消息
  2. 吉利屋短链接api对接教程
  3. 【前端 · 面试 】HTTP 总结(一)—— HTTP
  4. 删除、修改省名称
  5. json 基础啊,xml异步请求,post和get区别
  6. 模板字面量、标签函数、解构赋值与对象字
  7. PHP与JS数据遍历
  8. PHP基础学习与软件搭建
  9. 作业标题:0802-php是什么及运行原理 编程
  10. 【前端 · 面试 】HTTP 总结(二)—— HTTP