1. 程式人生 > >Android開發筆記(一百四十九)約束佈局ConstraintLayout

Android開發筆記(一百四十九)約束佈局ConstraintLayout

約束佈局ConstraintLayout是Android Studio 2.2推出的新佈局,並從Android Studio 2.3開始成為預設佈局檔案的根佈局,由此可見Android官方對其寄予厚望,那麼約束佈局究竟具備哪些激動人心的特性呢?

傳統的佈局如線性佈局LinearLayout、相對佈局RelativeLayout等等,若要描繪不規則的複雜介面,往往需要進行多重的佈局巢狀,不但僵硬死板缺乏靈活性,並且巢狀過多拖慢頁面渲染速度。約束佈局正是為了解決這些問題應運而生,它兼顧靈活性和高效率,可以看作是相對佈局的升級版,在很大程度上改善了Android的使用者體驗。開發者使用約束佈局之時,有多種手段往該佈局內新增和拖動控制元件,既能像原型設計軟體AxureRP那樣在畫板上任意拖曳控制元件,也能像傳統佈局那樣在XML檔案中調整控制元件佈局,還能在程式碼中動態修改控制元件物件的位置狀態,下面分別介紹約束佈局的這幾種使用方式:


在畫板上拖曳控制元件

設計師通過工具軟體三兩下就勾勒出介面原型,程式設計師卻得一個控制元件一個控制元件地小心佈局,並對控制元件位置不斷微調以符合原型上的尺寸比例。Android原先的介面手工編碼一直為人所詬病,因為“所見即所得”才是介面編碼的理想方式,比如iOS很早就在Xcode中集成了故事板,使得iOS程式設計師能夠像設計師那樣在畫板上拖動控制元件,從而加快了介面編碼的工作效率。自從ConstraintLayout誕生之後,Android程式設計師終於跟上時代步伐,也能在約束佈局內部隨意拖曳控制元件,同時存在主從關係的控制元件之間,附庸控制元件會跟隨目標控制元件一起移動,從而省卻了介面微調的大量勞動。
畫板上的控制元件拖動操作,三言兩語說不清楚,還是觀看具體的動圖比較一目瞭然:



在XML檔案中調整控制元件佈局

傳統佈局如線性佈局、相對佈局基本是在XML檔案中手工新增控制元件節點,約束佈局當然也允許在佈局檔案中指定控制元件的相對位置,這跟相對佈局內部的控制元件位置調整類似,只不過用來表示位置的屬性換了個名字罷了。與控制方位有關的屬性說明如下所示:
layout_constraintTop_toTopOf : 該控制元件的頂部與另一個控制元件的頂部對齊
layout_constraintTop_toBottompOf : 該控制元件的頂部與另一個控制元件的底部對齊
layout_constraintBottom_toTopOf : 該控制元件的底部與另一個控制元件的頂部對齊
layout_constraintBottom_toBottomOf : 該控制元件的底部與另一個控制元件的底部對齊
layout_constraintLeft_toLeftOf : 該控制元件的左側與另一個控制元件的左側對齊
layout_constraintLeft_toRightOf : 該控制元件的左側與另一個控制元件的右側對齊
layout_constraintRight_toLeftOf : 該控制元件的右側與另一個控制元件的左側對齊
layout_constraintRight_toRightOf : 該控制元件的右側與另一個控制元件的右側對齊
下面是一個運用約束佈局的XML檔案例子:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cl_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="40dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginLeft="200dp"
        android:background="@color/blue"
        android:text="我是山大王"
        android:textSize="17sp"
        android:textColor="@color/black" />

    <TextView
        android:id="@+id/tv_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        app:layout_constraintTop_toBottomOf="@+id/tv_first"
        android:layout_marginLeft="20dp"
        app:layout_constraintLeft_toLeftOf="@+id/tv_first"
        android:background="@color/blue"
        android:text="我是巡山的小嘍囉"
        android:textSize="17sp"
        android:textColor="@color/black" />
</android.support.constraint.ConstraintLayout>
與該佈局檔案對應的效果介面如下圖所示:



在程式碼中新增控制元件

若要利用程式碼給約束佈局動態新增控制元件,則可照常呼叫addView方法,不同之處在於,新控制元件的佈局引數必須使用約束佈局的佈局引數,即ConstraintLayout.LayoutParams,該引數通過setMargins/setMarginStart/setMarginEnd方法設定新控制元件與周圍控制元件的間距,至於新控制元件與周圍控制元件的位置約束關係,則可參照ConstraintLayout.LayoutParams的下列屬性說明:
topToTop : 當前控制元件的頂部與指定ID的控制元件頂部對齊
topToBottom : 當前控制元件的頂部與指定ID的控制元件底部對齊
bottomToTop : 當前控制元件的底部與指定ID的控制元件頂部對齊
bottomToBottom : 當前控制元件的底部與指定ID的控制元件底部對齊
startToStart : 當前控制元件的左側與指定ID的控制元件左側對齊
startToEnd : 當前控制元件的左側與指定ID的控制元件右側對齊
endToStart : 當前控制元件的右側與指定ID的控制元件左側對齊
endToEnd : 當前控制元件的右側與指定ID的控制元件右側對齊
下面是在約束佈局中新增新控制元件的程式碼例子:
private void addNewView() {
    TextView tv = new TextView(this);
    tv.setText("長按刪除該文字");
    tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
    tv.setGravity(Gravity.CENTER);
    tv.setBackgroundColor(Color.YELLOW);
    ConstraintLayout.LayoutParams container = new ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.WRAP_CONTENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
    );
    //設定控制元件左側與另一個控制元件的左側對齊
    //水平方向上只能使用start和end,因為left和right可能無法奏效
    container.startToStart = mLastViewId;
    //設定控制元件頂部與另一個控制元件的底部對齊
    container.topToBottom = mLastViewId;
    container.setMargins(0, Utils.dip2px(this, 30), 0, 0);
    //左側間距要使用Start,不能用Left,因為set.applyTo方法會清空Left的間距
    container.setMarginStart(Utils.dip2px(this, 10));
    tv.setLayoutParams(container);
    tv.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View vv) {
            cl_content.removeView(vv);
            return true;
        }
    });
    mLastViewId += 1000;
    tv.setId(mLastViewId);
    cl_content.addView(tv);
}
新增新控制元件的效果動圖如下所示:



在程式碼中動態調整控制元件位置

有時根據使用者在介面上的操作,需要立即調整相關控制元件的顯示位置,這要在程式碼中修改控制元件的位置引數。既然新增控制元件時可以通過佈局引數指定控制元件位置,那麼調整控制元件位置一樣也可以通過佈局引數來實現,基本流程依次為:先呼叫getLayoutParams方法獲得當前的佈局引數->再指定新的控制元件約束關係及間距->最後呼叫setLayoutParams啟用新的佈局引數。
可是按照傳統的佈局引數方式存在諸多不便之處,比如以下幾點就很不合理:
1、控制元件約束關係的指定,與間距設定是分開的,其他人難以找到二者之間的對應關係;
2、setMargins方法同時設定上下左右四個方向的間距,無法單獨設定某個方向的間距;
3、佈局引數在啟用時立即生效,沒有漸變的過程,讓使用者覺得很突兀。從下面的動圖就看到這個位置一下子發生變化,使用者體驗很不好:

為了改進以上幾個問題,constraint-layout開發包從1.0.1本版開始,增加了新的約束設定類ConstraintSet,該工具針對這幾個問題分別給出了相應的解決方案:
1、提供connect方法,一次性指定存在約束關係的兩個控制元件,以及它們的間距;
2、提供setMargin方法,允許單獨設定上下左右某個方向的間距;
3、提供了漸變管理類TransitionManager,支援展示空間位置變化的切換動畫;
下面是使用ConstraintSet修改控制元件位置的具體程式碼:
private void moveView() {
    //使用動畫展示新舊約束關係的切換過程。如果刪掉這行則不展示切換動畫
    TransitionManager.beginDelayedTransition(cl_content);
    int margin = Utils.dip2px(this, isMoved?200:20);
    //需要下載最新的constraint-layout,才能使用ConstraintSet
    ConstraintSet set = new ConstraintSet();
    //複製原有的約束關係
    set.clone(cl_content);
    //清空該控制元件的約束關係
    //set.clear(tv_first.getId());
    //設定該控制元件的約束寬度
    //set.constrainWidth(tv_first.getId(), ConstraintLayout.LayoutParams.WRAP_CONTENT);
    //設定該控制元件的約束高度
    //set.constrainHeight(tv_first.getId(),ConstraintLayout.LayoutParams.WRAP_CONTENT);
    //設定該控制元件的頂部約束關係與間距
    //set.connect(tv_first.getId(), ConstraintSet.TOP, cl_content.getId(), ConstraintSet.BOTTOM, margin);
    //設定該控制元件的底部約束關係與間距
    //set.connect(tv_first.getId(), ConstraintSet.BOTTOM, cl_content.getId(), ConstraintSet.BOTTOM, margin);
    //設定該控制元件的左側約束關係與間距
    set.connect(tv_first.getId(), ConstraintSet.START, cl_content.getId(), ConstraintSet.START, margin);
    //設定該控制元件的右側約束關係與間距
    //set.connect(tv_first.getId(), ConstraintSet.END, cl_content.getId(), ConstraintSet.END, margin);
    //LEFT和RIGHT的margin不管用,只有START和END的margin才管用
    //set.setMargin(tv_init.getId(), ConstraintSet.START, 200);
    //啟用新的約束關係
    set.applyTo(cl_content);
    isMoved = !isMoved;
}
上述變更控制元件位置程式碼的對應效果圖如下所示,有了切換動畫這下看起來比較柔和了:



點此檢視Android開發筆記的完整目錄


__________________________________________________________________________
本文現已同步釋出到微信公眾號“老歐說安卓”,開啟微信掃一掃下面的二維碼,或者直接搜尋公眾號“老歐說安卓”新增關注,更快更方便地閱讀技術乾貨。