一日一学_(LayoutParams与MeasureSpec)

最近看开源项目时,往往对一些以前熟悉(现在模糊)的知识,导致无法继续思考.

LayoutParams是什么?

LayoutParams可以理解为是子控制在父容器中的布局信息对象 ,它封装了子控件摆放的位置、高、宽等信息。如:屏幕一块区域被子控件使用,将一个子控件添加到一个父容器中,我们需要告诉父容器布局的摆放位置,那么子控件可以通过LayoutParams封装信息通知父布局 。

手机

如:上图launcher界面 ,每个App图标都占据一个位置,也就是App图标都有一个位置的信息,这些位置信息及图标大小就是LayoutParams。

  • LayoutParams只是封装了宽高,宽和高可以设置三种值:
  1. 固定的值;
  2. MATCH_PARENT,填满(和父容器一样大小);
  3. WRAP_CONTENT,能裹住组件就好。

可以简单理解:LayoutParams就是子布局在父布局的一个信息位置对象。

俩个布局演示:

        relativeLayout = (RelativeLayout) findViewById(R.id.main);
        Button button = new Button(this);
        button.setText("button");
        button.setTextColor(Color.WHITE);
        button.setBackgroundColor(Color.BLACK);
        FrameLayout.LayoutParams lytp = new FrameLayout.LayoutParams(200, 200);
        lytp.setMargins(200,200,0,0);
        button.setLayoutParams(lytp);
        relativeLayout.addView(button);
view展示
        relativeLayout = (RelativeLayout) findViewById(R.id.main);
        RelativeLayout relative = new RelativeLayout(this);
        relative.setBackgroundColor(Color.WHITE);
        RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CO NTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        lp.addRule(RelativeLayout.CENTER_IN_PARENT);
        relative.setLayoutParams(lp);
        relativeLayout.addView(relative);

ViewGroup

这个ViewGroup怎么没有,自己想想怎么回事?

喝口水

View的MeasureSpec测量过程

在自定义View时,我们需要根据需求来控制View的尺寸。这时候我们需要重写onMeasure进行定制。而如何定制与MeasureSpec有很大关系,我们接下来进行分析。

我们利用debug来查看onMeasure的调用过程:


调用过程

我们主要查看measureChildWithMargins方法

protected void measureChildWithMargins(View child,
    int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
   //在测量中会根据View的Layoutparams与父容器所施加的规
   //则转化成对应子布局的MeasureSpec。

    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);
    //然后根据获取的子布局的 WidthMeasureSpec 和 HeightMeasureSpec 
    //对子 view 进行测量,这里会最终调用view的onMeasure进行测量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

上面可以看出WuXiaoView的LayoutParams 与父布局有紧密的关系。
通过getChildMeasureSpec()源码查看他们的关系:

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) {
    // 父容器精确模式.
    case MeasureSpec.EXACTLY:
       //子布局参数是固定值,比如"layout_width" = "25px"
       //那么子布局测量模式肯定是EXACTLY,测量的宽就是25px,
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
           //子控件的布局参数是"match_parent",也就是想占满父容器
           //而此时父容器是精确模式,也就是能确定自己的尺寸了,那子控件也能确定自己大小了
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
           //子控件的布局参数是"wrap_content",也可以重写根据自己的逻辑决定自己大小(大小肯定不能大于父容器的大小)
            //测量模式就是AT_MOST,测量大小就是父容器的size
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

   // 父控件最大模式(父控件还不知道自己的尺寸)
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            //子控件能确定自己大小,尽管父容器自己还不知道自己大小
           //所以优先设置子布局
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
              //子控件想要和父容器一样大,但父容器也不明确自己大小
             //那么子控件也是AT_MOST,并且最大值不会超过父容器大小
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //子控件根据自己逻辑决定大小(默认最大值不会超过父容器大小)
            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 = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

里面很多地方用到了MeasureSpec,接下来看看它的真面目。

MeasureSpec是什么?

sdk解释:MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度。
MeasureSpc的源码很简洁:

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        ........
    }

包含了View的三种测量模式:
测量模式为int类型(32bit),其中高2位用来封装MeasureMode。

  1. UNSPECIFIED - 00000000 00000000 00000000 00000000 父容器不对子布局有任何限制,要多大给多大(如: scrollview)
  2. EXACTLY - 01000000 00000000 00000000 00000000 父容器已经测量出子布局大小。
  3. AT_MOST - 10000000 00000000 00000000 00000000 父窗口限定了一个最大值给子子布局。

低30位用来封装size.

应用:
当父容器为wrap_content,子布局也是wrap_content的情况默认宽高为600x600。这时候需要重写MeasureSpec进行判断了,从上面我们分析,我们知道如果不自己设置子布局为剩余父容器大小

剩余父容器

这不是我想要的。
应用布局



  

WuXiaoView 布局部分代码

public class WuXiaoView extends View {

    ...
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthspecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthspecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightspecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightspecSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (widthspecMode) {
            case MeasureSpec.EXACTLY:
                width =widthspecSize;
                break;
            case MeasureSpec.AT_MOST:
                width =600;
                break;
        }
        switch (heightspecMode) {
            case MeasureSpec.EXACTLY:
                height =heightspecSize;
                break;
            case MeasureSpec.AT_MOST:
                height =600;
                break;
        }
        setMeasuredDimension(width,height);

        }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawText("wuxiao",width/2,height/2,paint);
    }
}


我要的结果

你可能感兴趣的:(一日一学_(LayoutParams与MeasureSpec))