Tìm hiểu ConstraintLayout là gì — một hệ thống định vị linh hoạt cho Android cho phép xây dựng hệ phân cấp view phẳng bằng cách sử dụng constraint (ràng buộc) thay vì LinearLayout và RelativeLayout lồng nhau. ConstraintLayout giải quyết vấn đề "địa ngục lồng nhau" (layout nesting hell), giảm độ sâu hệ phân cấp xuống một cấp và tăng tốc kết xuất màn hình. Thư viện là một phần của Jetpack và có sẵn từ Android 2.3 (API 9) thông qua support-library. Các cơ chế chính được mô tả trong tài liệu chính thức của Android.
Những điểm chính
ConstraintLayout là một ViewGroup từ thư viện AndroidX ConstraintLayout, được thiết kế để tạo giao diện linh hoạt và hiệu suất cao thông qua các ràng buộc khai báo. Không giống như LinearLayout sắp xếp các phần tử thành một dòng, hay RelativeLayout định vị phần tử tương đối so với các phần tử lân cận, ConstraintLayout cho phép neo mỗi phần tử tương đối so với bất kỳ phần tử nào khác và cha cùng lúc.
Thư viện được công bố tại Google I/O 2016 như một giải pháp tăng tốc kết xuất các màn hình phức tạp. Vấn đề chính mà ConstraintLayout giải quyết là lồng ghép bố cục. Mỗi ViewGroup lồng nhau thêm ít nhất hai lần đo (measure) và một lần bố trí (layout). Một màn hình có 4 cấp lồng nhau thực hiện 8 lần đo; ConstraintLayout với cùng chức năng chỉ thực hiện 2 lần. Theo Google (Android Performance Blog, 2017), thay thế ba LinearLayout lồng nhau bằng một ConstraintLayout giảm thời gian onMeasure xuống 40%.
Phiên bản hiện tại ConstraintLayout 2.1.4 hoạt động ổn định trên Android 2.3+ (API 9) qua AndroidX. Phiên bản 2.0 giới thiệu định vị tròn, Flow (tự động xuống dòng phần tử) và hỗ trợ MotionLayout. ConstraintLayout rất cần thiết để hiểu phát triển Android hiện đại — nó được sử dụng trong Jetpack Compose như khái niệm cơ bản về bổ từ, trong các mẫu mặc định của Android Studio và trong Material Design 3.
Hệ phân cấp phẳng của ConstraintLayout có nghĩa là tất cả các view con đều ở cùng một cấp lồng nhau. Thay vì đặt phần tử A trong LinearLayout, và LinearLayout trong RelativeLayout, tất cả các phần tử được gắn trực tiếp vào ConstraintLayout cha hoặc vào nhau thông qua các thuộc tính. Điều này mang lại: giảm tiêu thụ bộ nhớ (mỗi ViewGroup là một đối tượng trong Java heap), layout pass nhanh hơn (ít lời gọi đệ quy hơn) và hành vi dễ dự đoán hơn khi kích thước màn hình thay đổi.
Constraint (ràng buộc) là một kết nối giữa cạnh của một View (hoặc tâm của nó) và cạnh của View khác hoặc cha. Mỗi View có thể có tối đa 8 constraint: left, top, right, bottom, start, end, baseline và center. Tối thiểu, hai constraint vuông góc là đủ để định vị (ví dụ: top + left).
Định dạng thuộc tính: app:layout_constraint[Source]_to[Target]Of="[id]" — trong đó Source là cạnh được gắn (Left, Right, Top, Bottom, Start, End, Baseline) và Target là cạnh đích. Ví dụ: app:layout_constraintTop_toBottomOf="@+id/header" có nghĩa là "cạnh trên của phần tử hiện tại được gắn với cạnh dưới của phần tử header". Để gắn vào cha, sử dụng id parent.
Bias (độ lệch) là một tham số hoạt động khi có các constraint đối lập (left + right hoặc top + bottom). Giá trị từ 0 đến 1: 0 — ép vào cạnh trái/trên, 0.5 — ở giữa, 1 — về cạnh phải/dưới. Thuộc tính: layout_constraintHorizontal_bias (0.0–1.0) và layout_constraintVertical_bias. Lề được đặt bằng android:layout_margin* tiêu chuẩn, nhưng constraint và lề hoạt động độc lập: lề là khoảng cách từ constraint, không phải từ View lân cận.
Từ ConstraintLayout 1.1+, hỗ trợ kích thước theo phần trăm được thêm vào qua layout_constraintWidth_percent và layout_constraintHeight_percent. Giá trị 0.3 có nghĩa là 30% chiều rộng/cao của cha. Kết hợp với bias, điều này cho phép tạo bố cục thích ứng mà không cần lập trình.
Chuỗi (Chain) là một nhóm gồm hai View trở lên được kết nối bằng constraint hai chiều (A gắn với B, B gắn với A). Chuỗi tự động phân phối không gian giữa các phần tử theo một trong các chế độ: spread (đồng đều, có tính đến lề), spread_inside (đồng đều, các phần tử bên ngoài không có lề cạnh), packed (các phần tử ép lại với nhau bằng bias chung). Chế độ được đặt qua thuộc tính app:layout_constraintHorizontal_chainStyle hoặc layout_constraintVertical_chainStyle.
Guideline (đường hướng dẫn) là một View phụ trợ, vô hình trong thời gian chạy, xác định một đường để gắn kết. Guideline có thể nằm ngang hoặc dọc, được định vị bằng dp, phần trăm (app:layout_constraintGuide_percent) hoặc với khoảng cách từ cạnh (app:layout_constraintGuide_begin/end). Guideline không thể thiếu cho bố cục thích ứng — ví dụ: chia màn hình thành hai nửa bằng nhau bất kể kích thước thiết bị.
Theo Google I/O 2017, chuỗi với spread_inside hiệu quả hơn 15–20% so với LinearLayout lồng nhau với weight, vì chúng tránh được lần đo kép cần thiết để tính toán weight.
Barrier (rào chắn) là một View ảo tự động điều chỉnh vị trí dựa trên kích thước của một nhóm phần tử. Không giống như Guideline có vị trí cố định, Barrier bị "đẩy" bởi phần tử rộng nhất trong nhóm. Ví dụ: nếu bạn có tiêu đề và mô tả với độ dài không xác định, một Barrier gắn vào cạnh phải của văn bản rộng nhất cho phép bạn đặt một biểu tượng ngay sau chúng. Thuộc tính: app:barrierDirection (left, right, top, bottom, start, end) và app:constraint_referenced_ids (danh sách id phân cách bằng dấu phẩy).
Group là một container ảo quản lý khả năng hiển thị của nhiều View cùng lúc. Thay vì gọi setVisibility cho từng phần tử riêng lẻ, chỉ cần thay đổi khả năng hiển thị của một Group. Group không ảnh hưởng đến định vị — chỉ ảnh hưởng đến khả năng hiển thị. Flow là một trợ lý ảo để tạo bố cục "chảy": các phần tử tự động xuống dòng/cột mới khi không đủ không gian, giống như văn bản trong một đoạn. Flow hỗ trợ wrapMode: none, chain và aligned.
Các công cụ này (Barrier, Group, Flow, Guideline) được gọi là trợ lý ảo vì chúng không phải là View theo nghĩa cổ điển — chúng không chiếm không gian trong hệ phân cấp và không tham gia vào các sự kiện tiêu điểm hay chạm. Mục đích của chúng là đơn giản hóa việc bảo trì các bố cục phức tạp mà không cần thêm các container lồng nhau.
Một biểu mẫu đăng nhập đơn giản với trường email, trường mật khẩu và một nút. Tất cả các phần tử đều được gắn với cha, ngoại trừ nút — nó nằm dưới trường mật khẩu. Hệ phân cấp phẳng được sử dụng — cả ba phần tử đều ở cùng một cấp.
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/email_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/email_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp" />
<Button
android:id="@+id/login_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/password_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"
android:layout_marginHorizontal="16dp"
android:text="Đăng nhập" />
</androidx.constraintlayout.widget.ConstraintLayout>
Tất cả các phần tử có chiều rộng 0dp (match_constraint), nghĩa là chúng kéo dài từ constraint start đến end có tính đến lề ngang. Điều này tương đương với match_parent có lề, nhưng không có lồng ghép.
Ba nút được phân phối đều theo chiều ngang với lề cạnh. Chuỗi spread_inside đặt các nút bên ngoài ở các cạnh và nút ở giữa được căn giữa giữa chúng.
<Button
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_center"
android:text="Trái" />
<Button
android:id="@+id/btn_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_left"
app:layout_constraintRight_toLeftOf="@+id/btn_right"
android:text="Giữa" />
<Button
android:id="@+id/btn_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_center"
app:layout_constraintRight_toRightOf="parent"
android:text="Phải" />
Chuỗi được tạo tự động khi các phần tử có constraint hai chiều. Chế độ spread_inside được đặt trên bất kỳ phần tử nào của chuỗi qua app:layout_constraintHorizontal_chainStyle="spread_inside". Điều này loại bỏ nhu cầu sử dụng LinearLayout với weightSum và layout_weight.
Tạo hai cột bằng nhau bằng cách sử dụng Guideline dọc ở 50%. Phần tử bên trái được gắn với cha trái và cạnh phải của nó vào guideline; phần tử bên phải được gắn với cạnh trái của nó vào guideline và với cha phải.
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl_midpoint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/left_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/gl_midpoint"
android:layout_margin="8dp"
android:background="@color/card_background" />
<TextView
android:id="@+id/right_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/gl_midpoint"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="8dp"
android:background="@color/card_background" />
Guideline với tỷ lệ phần trăm 0.5 tự động thích ứng với chiều rộng màn hình. Trên cả máy tính bảng và điện thoại, tỷ lệ cột vẫn là 50/50. Đối với danh pháp trái/phải, hãy sử dụng thuộc tính start/end để tương thích RTL.
Bảng so sánh ba ViewGroup chính cho phát triển Android: ConstraintLayout, LinearLayout và RelativeLayout. Tiêu chí: tính linh hoạt, hiệu suất, độ phức tạp mã và trường hợp sử dụng.
| Đặc điểm | ConstraintLayout | LinearLayout | RelativeLayout |
|---|---|---|---|
| Lồng nhau | Phẳng (một cấp) | Yêu cầu lồng nhau cho bố cục phức tạp | Một cấp, nhưng tính linh hoạt hạn chế |
| Hiệu suất measure | 2 lần (~40% nhanh hơn) | 4+ lần với weight | 2 lần |
| Kích thước phần trăm | Có (guide_percent, width_percent) | Chỉ qua weight/frame | Không |
| Hỗ trợ RTL | Tích hợp (start/end) | Tích hợp | Qua start/end (API 17+) |
| Barrier/Group/Flow | Có (trợ lý ảo) | Không | Không |
| Hoạt ảnh MotionLayout | Có | Không | Không |
| Khi nào sử dụng | Tất cả bố cục phức tạp, màn hình >5 phần tử | Danh sách đơn hướng đơn giản, hàng có nút | Bố cục tương đối đơn giản (mã kế thừa) |
Theo Android Vitals (Google, 2025), các ứng dụng sử dụng ConstraintLayout làm container chính hiển thị trung bình 18% khung hình jank ít hơn khi kết xuất màn hình phức tạp so với các ứng dụng sử dụng LinearLayout lồng nhau. Tại IT Sectr, chúng tôi đã chuyển sang ConstraintLayout làm tiêu chuẩn cho tất cả bố cục XML vào năm 2018 — điều này đã giảm độ sâu hệ phân cấp màn hình trung bình từ 4,2 xuống 1,8 cấp và tăng tốc phát triển các biểu mẫu mới lên 25%.
Câu hỏi thường gặp
match_parent trong ConstraintLayout hoạt động như bình thường — nó kéo dài View đến kích thước của cha. 0dp (match_constraint) có nghĩa là kích thước của View được tính từ các constraint: nếu các constraint left và right với lề được đặt, chiều rộng = parent — marginLeft — marginRight. Sự khác biệt trong hành vi: match_parent bỏ qua bias và có thể tràn ra ngoài trong khi hoạt ảnh; match_constraint tôn trọng tất cả các ràng buộc một cách chính xác và được Google khuyến nghị là chế độ chính cho ConstraintLayout.
Sử dụng kết hợp: kích thước phần trăm (layout_constraintWidth_percent) cho các phần tử chiếm một phần màn hình; Guideline với phần trăm để chia màn hình thành các khu vực; Barrier để định vị tương đối so với nội dung động; Flow với wrapMode để xuống dòng các thẻ. Một cách tiếp cận thay thế là sử dụng SlidingPaneLayout kết hợp với ConstraintLayout cho giao diện master-detail trên máy tính bảng.
Jetpack Compose không sử dụng ConstraintLayout như một ViewGroup, nhưng cung cấp phiên bản ConstraintLayout cho compose (androidx.constraintlayout:constraintlayout-compose) với cùng API trong Kotlin DSL: createRefFor(), constrainAs(), linkTo(), chain(), guideFrom(). Điều này hữu ích cho các bố cục phức tạp dễ mô tả qua constraint hơn là qua Column/Row. Tuy nhiên, trong Compose, nên bắt đầu với Column/Row/Box và chỉ chuyển sang ConstraintLayout khi cần định vị tương đối phức tạp.
Trong Android Studio, mở Layout Inspector (Tools → Layout Inspector), chọn ứng dụng đang chạy và di chuột qua phần tử có vấn đề. Bạn sẽ thấy tất cả các constraint, lề, đệm và bias trong biểu diễn 3D. Đối với XML, sử dụng bảng Design trong trình chỉnh sửa bố cục — nó làm nổi bật xung đột constraint bằng màu vàng và constraint thiếu bằng màu đỏ. Trong mã, đảm bảo mỗi View có hai constraint vuông góc, nếu không phần tử sẽ ở vị trí (0,0).
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm