Giải thích kiến thức cơ bản về LinearLayout — một ViewGroup cơ bản trong Android SDK để căn chỉnh các phần tử con trên một dòng: theo chiều dọc (LinearLayout.VERTICAL) hoặc chiều ngang (LinearLayout.HORIZONTAL). LinearLayout vẫn là cách đơn giản và nhanh nhất để xây dựng một hàng nút, danh sách cài đặt hoặc biểu mẫu nhập liệu khi độ phức tạp của bố cục không vượt quá 5–10 phần tử. Các thuộc tính layout_weight và orientation quản lý việc phân phối không gian và hướng. Thông số kỹ thuật chi tiết — trong LinearLayout API Reference.
Những điểm chính
LinearLayout — một trong những ViewGroup đầu tiên trong Android SDK (API Level 1), được thiết kế để sắp xếp các phần tử con tuần tự trong một hàng hoặc cột duy nhất. Mỗi phần tử mới được đặt sau phần tử trước đó và hướng được thiết lập bởi thuộc tính android:orientation. LinearLayout là vùng chứa Android đơn giản và dễ dự đoán nhất, khiến nó trở thành lựa chọn lý tưởng cho các giao diện tuyến tính: hàng nút, danh sách cài đặt với biểu tượng và văn bản, menu ngang.
Về mặt lịch sử, LinearLayout là ViewGroup đầu tiên mà một nhà phát triển Android mới bắt đầu làm quen. Ưu điểm của nó là tính minh bạch: các phần tử con được đặt chính xác theo thứ tự khai báo, không có quy tắc định vị ẩn. Không giống như RelativeLayout, nơi một phần tử có thể được gắn vào bất kỳ phần tử lân cận nào, hoặc ConstraintLayout với hệ thống ràng buộc của nó, LinearLayout hoạt động theo nguyên tắc "đầu tiên bên trái/trên, thứ hai sau nó, thứ ba sau thứ hai". Theo Google I/O 2017, 78% màn hình trong top 1000 ứng dụng Google Play sử dụng LinearLayout làm vùng chứa chính hoặc phụ trợ.
Số liệu chính của LinearLayout là LinearLayout.MeasureSpec — hệ thống quản lý kích thước. Với hướng VERTICAL, chiều rộng của mỗi phần tử bằng chiều rộng của phần tử cha (match_parent hoặc cố định) và chiều cao được xác định bởi nội dung hoặc thuộc tính. Đối với HORIZONTAL thì ngược lại: chiều cao bằng của phần tử cha, chiều rộng theo nội dung. Nếu trong một hàng HORIZONTAL, tổng chiều rộng của các phần tử vượt quá chiều rộng của phần tử cha, các phần tử có thể tràn ra ngoài màn hình (nếu không sử dụng weight).
LinearLayout ghi đè onMeasure và onLayout từ ViewGroup. Với hướng VERTICAL, onLayout đặt các phần tử dọc theo trục Y: phần tử con đầu tiên — y=0, thứ hai — y=prev.bottom + divider.hidden, v.v. Với HORIZONTAL — dọc theo trục X. Dấu phân cách được thiết lập qua android:divider và android:showDividers (beginning, middle, end). Dấu phân cách có thể là màu sắc, drawable hoặc tài nguyên Shape tùy chỉnh.
Hướng — một thuộc tính chính của LinearLayout xác định hướng sắp xếp các phần tử con. android:orientation="vertical" sắp xếp các phần tử từ trên xuống dưới; android:orientation="horizontal" — từ trái sang phải (hoặc từ phải sang trái với bản địa hóa RTL). Nếu hướng không được thiết lập, HORIZONTAL được sử dụng theo mặc định (trên các phiên bản cũ) — nhưng chỉ định rõ ràng là cần thiết để có hành vi có thể dự đoán được.
android:gravity — căn chỉnh nội dung bên trong LinearLayout. Chấp nhận các kết hợp: top, bottom, left, right, center, center_horizontal, center_vertical, fill, clip_vertical, clip_horizontal. Giá trị gravity="center" cho bố cục VERTICAL sẽ căn giữa tất cả các phần tử theo chiều ngang. android:layout_gravity — một thuộc tính của phần tử con căn chỉnh nó bên trong LinearLayout cha. Ví dụ: một nút có layout_gravity="right" trong bố cục VERTICAL sẽ đẩy nút về cạnh phải.
Sự khác biệt: gravity — thuộc tính của cha (cách các phần tử con được sắp xếp bên trong nó), layout_gravity — thuộc tính của con (cách nó hoạt động bên trong cha). Nhầm lẫn các thuộc tính này là một trong những lỗi phổ biến nhất ở người mới bắt đầu.
Đối với bố cục HORIZONTAL có các phần tử văn bản (TextView, Button, EditText), android:baselineAligned="true" rất hữu ích — nó căn chỉnh tất cả văn bản theo đường cơ sở của phông chữ, bất kể padding, kích thước hoặc chiều cao của chúng. Điều này làm cho một hàng có biểu tượng, tiêu đề và mô tả trở nên gọn gàng về mặt thị giác: văn bản không nhảy lên xuống.
layout_weight — một thuộc tính của phần tử con của LinearLayout xác định phần không gian trống mà nó sẽ chiếm. Nó chỉ hoạt động theo hướng của hướng (chiều rộng cho HORIZONTAL, chiều cao cho VERTICAL). Công thức tính: kích thước = kích thước riêng + (không gian trống × (trọng lượng phần tử / tổng tất cả trọng lượng)). Không gian trống = kích thước cha — tổng tất cả kích thước riêng của các phần tử con.
layout_weight hiệu quả hơn so với kích thước cố định cho các giao diện thích ứng. Ví dụ: nếu bảng điều khiển bên trái chiếm 30% màn hình và bên phải 70%, hãy đặt weight=3 và weight=7. Để hoạt động chính xác, kích thước theo hướng thường được đặt thành 0dp (android:layout_width="0dp" cho HORIZONTAL), khi đó kích thước riêng của phần tử được coi là không và toàn bộ kích thước chỉ được xác định bởi weight. Đây là khuyến nghị tiêu chuẩn của Google (Android Developers Guide, 2024).
Quan trọng: trọng lượng được tính tổng cho tất cả các phần tử trong LinearLayout, bao gồm cả những phần tử không có weight được đặt (trọng lượng của chúng được coi là 0). Nếu một phần tử có weight=1 và phần tử khác có weight=2, phần tử đầu tiên sẽ chiếm 1/3 không gian trống, phần tử thứ hai — 2/3. weightSum (một thuộc tính của cha) cho phép đặt tổng trọng lượng tối đa — nếu tổng trọng lượng của các phần tử con nhỏ hơn weightSum, phần chênh lệch sẽ là không gian trống.
Biểu mẫu đăng nhập bao gồm ba phần tử: trường email (chiếm 2/4 chiều cao màn hình), trường mật khẩu (1/4) và nút (1/4). Cả ba phần tử có weight chỉ hoạt động theo hướng VERTICAL theo chiều cao.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:hint="Email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:hint="Mật khẩu"
android:inputType="textPassword" />
<Button
android:id="@+id/login_button"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Đăng nhập" />
</LinearLayout>
Chiều cao của mỗi phần tử = (parent_height — padding) × (weight / sumWeights). email nhận 2/4 chiều cao, mật khẩu và nút — mỗi cái 1/4. Tất cả height=0dp, do đó kích thước riêng không được xem xét — chỉ weight hoạt động.
Một bảng điều khiển với ba nút: biểu tượng quay lại (wrap_content), tiêu đề (điền phần còn lại) và biểu tượng menu (wrap_content). weight cho tiêu đề = 1, các nút không có weight.
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical">
<ImageButton
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_back" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Tiêu đề"
android:textSize="18sp" />
<ImageButton
android:id="@+id/btn_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_more_vert" />
</LinearLayout>
Các nút quay lại và menu chiếm chính xác không gian cần thiết cho biểu tượng. Tiêu đề (TextView) với width=0dp và weight=1 mở rộng ra toàn bộ không gian còn lại giữa chúng. gravity="center_vertical" căn giữa tất cả các phần tử theo chiều dọc.
Một thẻ sản phẩm: hàng ngang (hình ảnh + khối văn bản dọc). LinearLayout bên ngoài HORIZONTAL, bên trong — VERTICAL cho tiêu đề, mô tả và giá.
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/product_image"
android:scaleType="centerCrop" />
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tên sản phẩm"
android:textStyle="bold"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mô tả ngắn sản phẩm trong hai dòng"
android:textSize="14sp"
android:textColor="@color/gray" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1.299 RUB"
android:textSize="18sp"
android:textColor="@color/accent" />
</LinearLayout>
</LinearLayout>
Hình ảnh bên trái (80×80dp), bên phải — một khối dọc gồm ba dòng văn bản. LinearLayout bên trong có weight=1, do đó nó chiếm tất cả không gian còn lại sau hình ảnh. Đây là mẫu thẻ danh sách cổ điển được tìm thấy trong hàng nghìn ứng dụng.
Nhược điểm chính của LinearLayout — sự cần thiết của các vùng chứa lồng nhau cho bố cục phức tạp. Nếu bạn cần sắp xếp các phần tử trong lưới 2×2 hoặc tạo một hàng có căn chỉnh ở cả hai cạnh, cần ít nhất 2–3 cấp độ lồng nhau. Mỗi cấp độ LinearLayout thêm 2 lần truyền onMeasure, trên các thiết bị yếu dẫn đến độ trễ đáng kể trong quá trình kết xuất.
Theo Android Performance Blog (Google, 2017), mỗi cấp độ lồng nhau bổ sung làm tăng thời gian bố cục thêm 30–50 μs. Đối với danh sách 20 mục với 3 cấp độ lồng nhau, độ trễ có thể đạt tới 3 ms cho mỗi phần tử — đây là hiện tượng giật (jank) đáng chú ý khi cuộn. ConstraintLayout giải quyết vấn đề này thông qua hệ thống phân cấp phẳng, thay thế 3–4 LinearLayout lồng nhau bằng một vùng chứa ràng buộc duy nhất.
Khuyến nghị: sử dụng LinearLayout cho bố cục tuyến tính đơn giản (tối đa 10 phần tử, một cấp độ lồng nhau). Đối với thẻ danh sách, biểu mẫu có căn chỉnh khác nhau và màn hình có cấu trúc bảng, hãy chọn ConstraintLayout. Đối với các phần tử lặp lại trong RecyclerView, hãy sử dụng ConstraintLayout trong tệp bố cục mục — điều này rất quan trọng cho hiệu suất cuộn.
Câu hỏi thường gặp
Lý do phổ biến nhất — kích thước theo hướng không được đặt thành 0dp. Đối với bố cục HORIZONTAL bạn cần android:layout_width="0dp", đối với VERTICAL — android:layout_height="0dp". Nếu phần tử có wrap_content hoặc match_parent, weight có thể không hoạt động hoặc cho kết quả không mong muốn. Lý do thứ hai — weightSum trên cha nhỏ hơn tổng trọng lượng của các phần tử con. Lý do thứ ba — sử dụng match_parent trên phần tử con theo hướng ngược lại.
layout_weight được đặt trên mỗi phần tử con và xác định phần của nó trong không gian trống. weightSum — một thuộc tính của LinearLayout cha đặt tổng trọng lượng tối đa. Theo mặc định, weightSum = tổng tất cả layout_weight của các phần tử con. Nếu bạn đặt weightSum = 3 và các phần tử con có weight=1, mỗi phần tử sẽ chiếm 1/3. Nếu chỉ có hai phần tử con với weight=1, 1/3 còn lại sẽ trống. weightSum thuận tiện để cố định tỷ lệ mà không cần tính toán lại khi thêm/xóa phần tử.
Theo mặc định không có khoảng cách — các phần tử được đặt liền kề nhau. Nếu khoảng cách xuất hiện, hãy kiểm tra: android:divider và android:showDividers trên cha, android:layout_margin trên các phần tử con. Ngoài ra, android:weightSum có thể ảnh hưởng — nếu các phần tử sử dụng weight, có thể còn khoảng trống giữa chúng do tính toán không chính xác. Để không có khoảng cách, đặt tất cả lề của phần tử con thành 0dp và loại bỏ dấu phân cách.
Đối với bố cục đơn hướng đơn giản (1–5 phần tử trong một hàng/cột) LinearLayout nhanh hơn một chút — nó không tốn tài nguyên cho việc tính toán ràng buộc. Đối với bố cục phức tạp (hơn 5 phần tử, lưới, các loại căn chỉnh khác nhau) ConstraintLayout nhanh hơn vì nó sử dụng hệ thống phân cấp phẳng thay vì LinearLayout lồng nhau. Theo các thử nghiệm của Google (Android Performance Blog), ConstraintLayout chiến thắng trên các bố cục yêu cầu 3+ cấp độ lồng nhau LinearLayout.
Tóm tắ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