一日一学_(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进行判断了,从上面我们分析,我们知道如果不自己设置子布局为剩余父容器大小

剩余父容器

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
  <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />

   <com.wuxiao.wuxiaodemo.WuXiaoView
       android:background="@color/colorAccent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />
</LinearLayout>

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);
    }
}


我要的结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容