一日一學(xué)_(LayoutParams與MeasureSpec)

最近看開源項目時,往往對一些以前熟悉(現(xiàn)在模糊)的知識,導(dǎo)致無法繼續(xù)思考.

LayoutParams是什么?

LayoutParams可以理解為是子控制在父容器中的布局信息對象 ,它封裝了子控件擺放的位置、高、寬等信息。如:屏幕一塊區(qū)域被子控件使用,將一個子控件添加到一個父容器中,我們需要告訴父容器布局的擺放位置,那么子控件可以通過LayoutParams封裝信息通知父布局 。

手機

如:上圖launcher界面 ,每個App圖標(biāo)都占據(jù)一個位置,也就是App圖標(biāo)都有一個位置的信息,這些位置信息及圖標(biāo)大小就是LayoutParams。

  • LayoutParams只是封裝了寬高,寬和高可以設(shè)置三種值:
  1. 固定的值;
  2. MATCH_PARENT,填滿(和父容器一樣大?。?/li>
  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時,我們需要根據(jù)需求來控制View的尺寸。這時候我們需要重寫onMeasure進(jìn)行定制。而如何定制與MeasureSpec有很大關(guān)系,我們接下來進(jìn)行分析。

我們利用debug來查看onMeasure的調(diào)用過程:


調(diào)用過程

我們主要查看measureChildWithMargins方法

protected void measureChildWithMargins(View child,
    int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
   //在測量中會根據(jù)View的Layoutparams與父容器所施加的規(guī)
   //則轉(zhuǎn)化成對應(yīng)子布局的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);
    //然后根據(jù)獲取的子布局的 WidthMeasureSpec 和 HeightMeasureSpec 
    //對子 view 進(jìn)行測量,這里會最終調(diào)用view的onMeasure進(jìn)行測量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

上面可以看出WuXiaoView的LayoutParams 與父布局有緊密的關(guān)系。
通過getChildMeasureSpec()源碼查看他們的關(guān)系:

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:
       //子布局參數(shù)是固定值,比如"layout_width" = "25px"
       //那么子布局測量模式肯定是EXACTLY,測量的寬就是25px,
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
           //子控件的布局參數(shù)是"match_parent",也就是想占滿父容器
           //而此時父容器是精確模式,也就是能確定自己的尺寸了,那子控件也能確定自己大小了
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
           //子控件的布局參數(shù)是"wrap_content",也可以重寫根據(jù)自己的邏輯決定自己大小(大小肯定不能大于父容器的大小)
            //測量模式就是AT_MOST,測量大小就是父容器的size
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

   // 父控件最大模式(父控件還不知道自己的尺寸)
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            //子控件能確定自己大小,盡管父容器自己還不知道自己大小
           //所以優(yōu)先設(shè)置子布局
            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) {
            //子控件根據(jù)自己邏輯決定大小(默認(rèn)最大值不會超過父容器大小)
            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 父容器已經(jīng)測量出子布局大小。
  3. AT_MOST - 10000000 00000000 00000000 00000000 父窗口限定了一個最大值給子子布局。

低30位用來封裝size.

應(yīng)用:
當(dāng)父容器為wrap_content,子布局也是wrap_content的情況默認(rèn)寬高為600x600。這時候需要重寫MeasureSpec進(jìn)行判斷了,從上面我們分析,我們知道如果不自己設(shè)置子布局為剩余父容器大小

剩余父容器

這不是我想要的。
應(yīng)用布局

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


我要的結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容