約束布局ConstraintLayout詳解與簡(jiǎn)單自定義布局

約束布局

介紹 :

在2016年的Google I/O大會(huì)上 , Google 發(fā)布了Android Studio 2.2預(yù)覽版,同時(shí)也發(fā)布了Android 新的布局方案 ConstraintLayout , 但是最近的一年也沒有大規(guī)模的使用。2017年Google發(fā)布了 Android Studio 2.3 正式版,在 Android Studio 2.3 版本中新建的Module中默認(rèn)的布局就是 ConstraintLayout ,ConstraintLayout向下兼容 API 9;

優(yōu)點(diǎn) :

1、可以極大程度的降低布局的嵌套層級(jí),集合了幾大布局的優(yōu)點(diǎn);

使用 :

1、AndroidStudio 2.2 需要自己添加 ConstraintLayout依賴 ;

    compile 'com.android.support.constraint:constraint-layout:1.0.1';

同時(shí)還需要添加倉庫,因?yàn)榧s束布局是google()mevan倉庫中的

    maven { url 'https://maven.google.com' }

我們公司的的項(xiàng)目需要兩者都加,如果沒有加倉庫,可能本地不會(huì)報(bào)錯(cuò),但是在jekens上會(huì)編譯不過

2、AndoroidStudio 2.3 之后,默認(rèn)添加了布局依賴

添加依賴后直接在xml布局引用即可

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.constraintlayout.app.Main2Activity"> 

</android.support.constraint.ConstraintLayout>

常用屬性 :

設(shè)置控件大小

layout_width    // 設(shè)置控件的寬

layout_height   // 設(shè)置控件的高

以上兩個(gè)屬性是用于設(shè)置控件的大小,相比較之前的布局的大小略有不同

wrap_content    // 控件的大小為自適應(yīng)大小,按內(nèi)容填充

match_parent    // 控件的大小,父布局大小一樣大,就算添加了各邊的約束也一樣

0dp             // 控件的大小,與為設(shè)置的約束大小,大小就為四邊的約束大小

除了以上方式還有以下兩種方式用來設(shè)置控件的大小

layout_constraintHeight_percent // 設(shè)置控件的高度相對(duì)父布局的百分比 大小在0~1.0 中

layout_constraintWidth_percent  // 設(shè)置控件的寬度相對(duì)父布局的百分比 大小在0~1.0 中

上述這種方式,設(shè)置的寬度和高度都必須為0dp 不然的話屬性無法起作用

layout_constraintDimensionRatio="4:3"

還有一種方式為通過設(shè)置寬高比來設(shè)置控件的大小 ,但是這種方式有一點(diǎn)需要注意,需要知道寬大小或者高度的大小,只有一方已知大小后,另一方的大小為0dp,才能生效,因?yàn)榇笮⌒枰鶕?jù)比值計(jì)算得出

替換RelativeLayout

layout_constraintTop_toTopOf // 將所需視圖的頂部與另一個(gè)視圖的頂部對(duì)齊。
layout_constraintTop_toBottomOf // 將所需視圖的頂部與另一個(gè)視圖的底部對(duì)齊。 

layout_constraintBottom_toTopOf // 將所需視圖的底部與另一個(gè)視圖的頂部對(duì)齊。 
layout_constraintBottom_toBottomOf // 將所需視圖的底部與另一個(gè)視圖的底部對(duì)齊。 

layout_constraintLeft_toLeftOf // 將所需視圖的左邊與另一個(gè)視圖的左邊對(duì)齊。 
layout_constraintLeft_toRightOf // 將所需視圖的左邊與另一個(gè)視圖的右邊對(duì)齊。 

layout_constraintRight_toLeftOf // 將所需視圖的右邊與另一個(gè)視圖的左邊對(duì)齊。 
layout_constraintRight_toRightOf // 將所需視圖的右邊與另一個(gè)視圖的右邊對(duì)齊。

當(dāng)我們將一個(gè)控件四個(gè)方向都約束,如下所示

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/constraintLayout"
tools:context="com.constraintlayout.app.MainActivity"
>
 <Button
    android:id="@+id/button3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />
 </android.support.constraint.ConstraintLayout>
約束示例

可以看到 對(duì)應(yīng)的控件上下左右中間都有一個(gè)點(diǎn)連著父布局的邊界 ,后面可以跟對(duì)應(yīng)的id,而上面那八組屬性就是控制這個(gè)連接的位置,這四個(gè)屬性不是每個(gè)每個(gè)屬性都必須,就拿上面那個(gè)例子,假設(shè)只有左跟上兩個(gè)屬性,那么控件就會(huì)自動(dòng)吸附到父布局左上角,有點(diǎn)類似相對(duì)布局,但是比起相對(duì)布局而言在有些方面簡(jiǎn)單很多 ,比如說控件的左右對(duì)齊,上下對(duì)齊,通過這幾個(gè)屬性,就很容易實(shí)現(xiàn);

假設(shè)通過直接設(shè)置到對(duì)應(yīng)控件的約束不好實(shí)現(xiàn)的話,可能在那個(gè)位置沒有控件,比如說,我需要將一個(gè)TextView 設(shè)置到橫向30%的位置,那么由于在30% 的位置,沒有對(duì)應(yīng)的控件來提供約束,那么該怎么實(shí)現(xiàn)呢

<android.support.constraint.Guideline
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.1"
    android:layout_width="wrap_content"
    android:layout_height="match_parent" />

可以通過上述的Guideline 在指定位置畫一條輔助線,這樣就可以實(shí)現(xiàn)在對(duì)應(yīng)的位置繪制一條輔助線,就可以實(shí)現(xiàn)對(duì)應(yīng)的約束了,而且為了方便確定輔助線的位置 提供了如下的屬性

 layout_constraintGuide_percent="0.1"   // 設(shè)置基線到在橫向或者豎向的相對(duì)父布局的百分比

 layout_constraintGuide_begin="xxdp"    // 設(shè)置基線橫向或者豎向相對(duì)左上角的位置

 layout_constraintGuide_end="xxdp"      // 設(shè)置基線相對(duì)終點(diǎn)的位置

這樣就可以通過基線,準(zhǔn)確的將控件設(shè)置到對(duì)應(yīng)的位置,這些屬性是可以通過代碼更改的,優(yōu)先順序?yàn)閜ercent-> begin -> end;

layout_constraintHorizontal_bias //控件的水平偏移比例 

layout_constraintVertical_bias //控件的垂直偏移比例

這樣連接后,默認(rèn)視圖會(huì)顯示到父布局的中間位置,四邊約束后,布局顯示的位置不靠中,則可以通過以上兩個(gè)屬性設(shè)置顯示的位置,因?yàn)檫@兩個(gè)屬性默認(rèn)值都為0.5,所以顯示在中間 ,如果更改其值,就可以顯示到不同的位置

layout_constraintVertical_chainStyle // 豎直鏈的設(shè)置模式

layout_constraintHorizontal_chainStyle  // 水平鏈的設(shè)置模式

通過以上八組屬性就可以輕易的實(shí)現(xiàn)RelativeLayout的常用功能,但是這八組屬性有一個(gè)問題,設(shè)置負(fù)的值margin或padding值是無效的;

替換LinearLayout功能

在約束布局中,如何實(shí)現(xiàn)線性布局的效果呢,其實(shí)在上述的八個(gè)屬性,就可以使得控件橫向或者豎向排布,而LinearLayout 有一個(gè)特殊點(diǎn)是可以使布局按照權(quán)重來分配,其實(shí)在約束布局中也有相應(yīng)的屬性

layout_constraintHorizontal_weight  // 在橫向?qū)崿F(xiàn)鏈的基礎(chǔ)上,使控件按照對(duì)應(yīng)的權(quán)重分配

layout_constraintVertical_weight    // 在縱向?qū)崿F(xiàn)鏈的基礎(chǔ)上,使控件大小按照權(quán)重分配

那么以上的內(nèi)容都提到了一個(gè)鏈,那么什么才是鏈呢,如下圖所示:


chainDemo.jpg

假設(shè)btn1的 layout_constraintRight_toLeftOf 屬性連接的btn2 而btn2 的layout_constraintLeft_toRightOf 這個(gè)屬性連接的是btn1 ,那么這兩個(gè)控件之間的結(jié)構(gòu)就是鏈結(jié)構(gòu)了,而如果你想使用上面的橫向weight屬性的話,那么必須在在橫向每個(gè)控件之間都是鏈結(jié)構(gòu),并且實(shí)現(xiàn)了最左邊控件的左約束,以及最右邊控件的右約束,并且每個(gè)控件的width 都必須為0dp 與LinearLayout一樣,就可以實(shí)現(xiàn)而LinearLayout這種效果了;

layout_constraintHorizontal_chainStyle

layout_constraintVertical_chainStyle

以上兩個(gè)屬性是設(shè)置對(duì)應(yīng)鏈的模式,模式有以下三種:

spread : 它將平分間隙讓多個(gè) Views 布局到剩余空間;
    
spread_inside :它將會(huì)把兩邊最邊緣的兩個(gè) View 到外向父組件邊緣的距離去除,然后讓剩余的 Views 在剩余的空間內(nèi)平分間隙布局

packed :它將所有 Views 打包到一起不分配多余的間隙(當(dāng)然不包括通過 margin 設(shè)置多個(gè) Views 之間的間隙),然后將整個(gè)組件組在可用的剩余位置居中

以上就是約束布局實(shí)現(xiàn)LinearLayout效果了

常用新特性 :

1、圓形定位 :

ayout_constraintCircle:引用另一個(gè)控件的 id。

layout_constraintCircleRadius:到另一個(gè)控件中心的距離。

layout_constraintCircleAngle:控件的角度(順時(shí)針,0 - 360 度)。

通過給控件設(shè)置如上三個(gè)屬性,就可以實(shí)現(xiàn)控件相對(duì)控件實(shí)現(xiàn)圓形定位

2、組的概念
Group 的作用就是控制一組控件的可見性。

<android.support.constraint.Group 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:visibility="gone" 
    app:constraint_referenced_ids="title, desc" />

使用過程中發(fā)現(xiàn)的問題

1、設(shè)置負(fù)邊距無效

2、guideline 在代碼中更改位置時(shí),當(dāng)改變的是對(duì)應(yīng)precent,begin ,end 三種模式切換時(shí),當(dāng)你付對(duì)應(yīng)的值并不會(huì)清空其他值,所以而且,其優(yōu)先級(jí)為precent > begin > end,所以如果需要更改時(shí)需要將對(duì)應(yīng)的值改為默認(rèn)值才會(huì)生效,precent 默認(rèn)值為-1 begin 和 end 默認(rèn)值也為-1;

3、需要注意match_parent 屬性,與LinearLayout 不一樣,不是填充好對(duì)應(yīng)的可用空間,而是父布局,當(dāng)設(shè)置width或者h(yuǎn)eight 為0dp時(shí),如果是指定約束來設(shè)置大小,那么必須把對(duì)應(yīng)橫向所有的約束補(bǔ)全,否則不會(huì)生效;

4、如果需要設(shè)置某個(gè)布局背景,可以通過View 設(shè)置backGround 來實(shí)現(xiàn);

以上就是今天約束布局部分的所有內(nèi)容,比較簡(jiǎn)單,而且在有些方面比原有布局更方便,并且能極大程度減少布局嵌套,所以,建議大家做布局優(yōu)化時(shí)可以試試;

自定義Layout

Layout在Android中主要起到對(duì)子View 進(jìn)行測(cè)量和布局的作用,通常情況下 如下圖所示:

布局測(cè)量過程.PNG

為什么要自定義Layout :

1、 由于有的常用布局無法解決項(xiàng)目需求 ,比如說 流式布局的等;
2、 可以通過自定義布局更了解自定義控件的一些內(nèi)容;平常我們寫自定義控件時(shí)往往是自定義一個(gè)View 著重于onDraw()這個(gè)方法,今天我們可以了解一下 onMeasure(),以及onLayout()的流程;

如何自定義布局 :

1、  重寫onMeasure()來計(jì)算內(nèi)部布局 : 
    1、調(diào)用每個(gè)子View的Measure(),讓子View自我測(cè)量;
    2、根據(jù)子View的尺寸,得到子View的位置,并保存他們的位置和尺寸;
    3、根據(jù)子View的位置和尺寸計(jì)算得出自己的尺寸,并用SetMeasureDimension()進(jìn)行保存;

2、 重寫onLayout()來擺放子View的位置:
    1、調(diào)用子View的layout方法,將對(duì)應(yīng)的位置以參數(shù)的形式傳進(jìn)去即可;

實(shí)例FlowLayout:

FlowLayout介紹 :

FlowLaout源碼解析:

public class FlowLayout extends ViewGroup {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 獲取父容器對(duì)布局的期望寬高 ,根據(jù)期望寬高 分別獲取長(zhǎng)寬的模式和值
        // widthMeasureSpec 其實(shí)是一個(gè)32位的數(shù) 其中前兩位代表模式 ,后30位代表具體的值
        // 模式分為3種
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        // wrap_content
        int width = 0;
        int height = 0;

        int lineWidth = 0;
        int lineHeight = 0;

        int cCount = getChildCount();

        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE) {
                if (i == cCount - 1) {
                    width = Math.max(lineWidth, width);
                    height += lineHeight;
                }
                continue;
            }
            // 測(cè)量子View 
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();

            int childWidth = child.getMeasuredWidth() + lp.leftMargin+ lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
                width = Math.max(width, lineWidth);
                lineWidth = childWidth;
                height += lineHeight;
                lineHeight = childHeight;
            } else {
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            if (i == cCount - 1) {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }

        // 保存FlowLaout的width和height ,當(dāng)為match_parent 以及指定大小是 mode 都為EXACTLY
        setMeasuredDimension(
                //
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
        );

    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllViews.clear();
        mLineHeight.clear();
        mLineWidth.clear();
        lineViews.clear();

        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;

        int cCount = getChildCount();

        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE) continue;
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
                mLineHeight.add(lineHeight);
                mAllViews.add(lineViews);
                mLineWidth.add(lineWidth);

                lineWidth = 0;
                lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
                lineViews = new ArrayList<View>();
            }
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                    + lp.bottomMargin);
            lineViews.add(child);

        }
        mLineHeight.add(lineHeight);
        mLineWidth.add(lineWidth);
        mAllViews.add(lineViews);


        int left = getPaddingLeft();
        int top = getPaddingTop();

        int lineNum = mAllViews.size();

        for (int i = 0; i < lineNum; i++) {
            lineViews = mAllViews.get(i);
            lineHeight = mLineHeight.get(i);

            // set gravity
            int currentLineWidth = this.mLineWidth.get(i);
            switch (this.mGravity) {
                case LEFT:
                    left = getPaddingLeft();
                    break;
                case CENTER:
                    left = (width - currentLineWidth) / 2 + getPaddingLeft();
                    break;
                case RIGHT:
                    //  適配了rtl,需要補(bǔ)償一個(gè)padding值
                    left = width - (currentLineWidth + getPaddingLeft()) - getPaddingRight();
                    //  適配了rtl,需要把lineViews里面的數(shù)組倒序排
                    Collections.reverse(lineViews);
                    break;
            }

            for (int j = 0; j < lineViews.size(); j++) {
                View child = lineViews.get(j);
                if (child.getVisibility() == View.GONE) {
                    continue;
                }

                MarginLayoutParams lp = (MarginLayoutParams) child
                        .getLayoutParams();

                int lc = left + lp.leftMargin;
                int tc = top + lp.topMargin;
                int rc = lc + child.getMeasuredWidth();
                int bc = tc + child.getMeasuredHeight();

                child.layout(lc, tc, rc, bc);

                left += child.getMeasuredWidth() + lp.leftMargin
                        + lp.rightMargin;
            }
            top += lineHeight;
        }

    }
}



// MeasureChildren 對(duì)子View進(jìn)行測(cè)量 
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);
}

// 具體的根據(jù)layoutparams 參數(shù)中的width和height 獲取對(duì)應(yīng)的 子View的MeasureSpec
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);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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