Tài trợ bài viết này và giới thiệu dịch vụ, sản phẩm, thương hiệu, nhu cầu tuyển dụng của doanh nghiệp đến với cộng đồng.
STDIO Khi bắt đầu với lập trình trên thiết bị di động chúng ta thường phải thiết kế giao diện cho ứng dụng, vì vậy chúng ta phải tìm hiểu những view hay control mà ứng dụng sử dụng, và cách layout những view, control này cho hợp lý.
Nội dung bài viết

Giới thiệu

Để thiết kế được một UI trong android, điều chắc chắn là các bạn phải biết cách sắp xếp (layout) các phần tử view. Trong android có hổ trợ chúng ta một số ViewGroup để chúng ta có thể layout các phần tử view theo những cách khác nhau, và các ViewGroup này layout như thế nào thì chúng ta sẽ cùng tìm hiểu rõ hơn trong bài viết này.

Tiền đề bài viết

Bài viết xuất phát từ niềm đam mê chia sẽ của tác tới các bạn đọc đam mê lập trình. Hy vọng những kiến thức nhỏ bé này sẽ giúp ích được cho các bạn trong học tập cũng như công việc mà các bạn đang gặp phải.

Đối tượng hướng đến

Những lập trình viên đang tìm hiểu lập trình trên android, và bài viết này hướng tới các bạn muốn hiểu tìm hiểu về Layout trong android.

Khái niệm View

Những gì mà chúng ta nhìn thấy trên màn hình device android như Button, EditText, ImageView, TextView, RadioButton, CheckBox, ListView, GridView... được gọi là View. Mỗi View sẽ có những chức năng khác nhau.

Ví dụ:

  • TextView là view dùng để hiện thị text lên màn hình.
  • EditText là view dùng để lấy nội dung mà người dùng nhập vào.
  • ImageView là view dùng để hiển thị hình ảnh.
  • Button là view dùng dùng để thực hiện một nhiệm vụ nào đó khi chúng ta nhấn vào button.

Trong android, class View được định nghĩa trong package: package android.view;

Chúng ta có thể tạo ra view mới theo ý của chúng ta bằng cách extends từ lớp View override lại các phương thức của View để làm việc với View mới của chúng ta. Tôi sẽ đề cập phần này ở các bài viết sau.

Chúng ta sẽ đều cập đến một số thuộc tính nổi bật của View.

layout_height

Thuộc tính này quy định chiều cao của view đó và thường có các giá trị như sau:

  • match_parent: chiều cao của view sẽ bằng đúng chiều cao của phần tử cha chứa nó.
  • wrap_content: chiều cao của view phụ thuộc vào content của view.
  • giá trị xác định: xác định chiều cao của view theo một đơn vị nào đó (dp, px, in, mm, sp...)

layout_width

  • match_width: chiều rộng của view sẽ bằng đúng chiều rộng của phần tử cha chứa nó.
  • wrap_content: chiều rộng của view phụ thuộc vào content của view.
  • giá trị xác định: xác định chiều rộng của view theo một đơn vị nào đó (dp, px, in, mm, sp...)

Khái niệm ViewGroup

Nhắc đến khái niệm ViewGroup chắc hẳn có nhiều bạn còn thấy lạ lẩm với nó, tôi cũng vậy, khi bắt đầu tôi cũng thấy nó rất lạ và không hiểu nó là cái gì. Nhưng đúng như cái tên của nó:

ViewGroup là một view chứa những view khác, ViewGroup có nhiệm vụ sẽ tính toán và sắp xếp (layout) các phần tử view con nằm bên trong nó theo quy luật nào đó. Nếu bạn muốn tạo ra ViewGroup mới để layout các view con trong nó theo cách của mình thì có thể extends từ lớp ViewGroup override lại hai phương thức là onLayout onMeasure.

Có khá nhiều ViewGroup được sử dụng trong android như FrameLayout, RelativeLayout, LinearLayout, TableLayout, CoordinateLayout... Nhưng phổ biến nhất là 3 ViewGroup sau:

  • FrameLayout
  • RelativeLayout
  • LinearLayout

Trong phạm vi bài viết này tôi sẽ cùng các bạn đi từ cơ bản tới nâng cao về 3 ViewGroup này để chúng ta có thể thiết kế bất cứ UI nào mà chúng ta mong muốn.

FrameLayout

FrameLayout là một ViewGroup được sử dụng rất nhiều trong android. Bởi vì nó là ViewGroup đơn giản nhất, và thời gian tính toán của nó để layout ra các view con trong nó là thấp nhất nên performence của ViewGroup này là cao nhất.

FrameLayout được định nghĩa bắt đầu bởi thẻ <FrameLayout> và thẻ đóng </FrameLayout>. Ở giữa thẻ đóng và thẻ mở chính là các view con của nó.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--View Child-->

</FrameLayout>

Quy tắc layout

Quy tắc layout các view con trong FrameLayout là các view sẽ nằng chồng lên nhau, view thêm vào sau sẽ nằm đè lên view nằm phía dưới. Ví dụ như sau:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"
        android:textColor="#2c3e50"
        android:textSize="32sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="STDIO"
        android:textColor="#16a085"
        android:textSize="32sp" />
</FrameLayout>

Với đoạn mã trên thì "STDIO" sẽ nằm chồng lên "Hello World":

stdio_frame_layout_1

Vậy tại sao chúng ta lại cần layout như thế này. Ví dụ tôi cần 1 tấm hình và tôi muốn hiện chữ lên trên tấm hình đó thì chúng ta sẽ cần FrameLayout. 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/avatar_author"/>

        <TextView
            android:layout_gravity="bottom|center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Nguyễn Nghĩa"
            android:textColor="#16a085"
            android:textSize="32sp" />
    </FrameLayout>

</FrameLayout>

Tên tác giả sẽ nằm đề lên trên hình ảnh

stdio_frame_layout_2

Ưu điểm

+ Là ViewGroup đơn giản nên thời gian tính toán để layout các view con nhanh.

Nhược điểm

+ Không thiết kế được cái giao diện phức tạp.

RelativeLayout

RelativeLayout ViewGroup cũng được sử dụng khá nhiều trong android, RelativeLayout được định nghĩa trong xml bởi cặp thẻ đóng mở <RelativeLayout> và thẻ đóng </RelativeLayout>.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <!--View child-->

</RelativeLayout>

Quy tắc layout

Quy tắc layout của RelativeLayout khá giống như FrameLayout, nhưng có một số điểm đặc biệt là các view có thể xác định bằng vị trí tương đối  (relative) với các view khác thông qua id.

Các vị trí tương đối này như sau:

  • android:layout_above="id_name": view hiện tại sẽ nằm phía trên view có thuộc tính id là id_name.
  • android:layout_below="id_name": view hiện tại sẽ nằm phía dưới view có thuộc tính id là id_name.
  • android:layout_toLeftOf="id_name": view hiện tại sẽ nằm bên trái dưới view có thuộc tính id là id_name.
  • android:layout_toRightOf="id_name": view hiện tại sẽ nằm phía bên phải view có thuộc tính id là id_name.

Cùng xét ví dụ để hiểu sâu hơn về RelativeLayout.

stdio_relative_layout_1

Ta thấy rằng EditText username nằm dưới TextView username. Và tương tự TextView password nằm dưới Editext username, EditText password sẽ nằm dưới TextView password.

Mã nguồn sẽ như sau:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="username" />

    <EditText
        android:id="@+id/edt_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_username" />

    <TextView
        android:id="@+id/tv_password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/edt_username"
        android:text="password" />

    <EditText
        android:id="@+id/edt_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_password"
        android:inputType="textPassword" />
    
</RelativeLayout>

Xét tiếp ví dụ như hình sau:

stdio_relative_layout_2

 

EditText username sẽ nằm bên phải TextView username

EditText password sẽ nằm bên phải TextView password

TextView password và EditText password sẽ nằm phía dưới TextView username

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/edt_username"
        android:text="username" />

    <EditText
        android:id="@+id/edt_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/tv_username" />

    <TextView
        android:id="@+id/tv_password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/edt_password"
        android:layout_below="@id/edt_username"
        android:text="password" />

    <EditText
        android:id="@+id/edt_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_username"
        android:layout_toRightOf="@id/tv_password"
        android:inputType="textPassword" />

</RelativeLayout>

Một số thuộc tính khác thường xuyên sử dụng với RelativeLayout:

  • android:layout_alignParentBottom="boolean": Căn dưới phần tử hiện theo phần tử cha nếu set là true.
  • android:layout_alignParentTop="boolean": Căn trên phần hiện tại theo phần tử cha nết set là true.
  • android:layout_alignParentRight="boolean": Căn phải phần tử hiện tại theo phần tử cha nếu set là true.
  • android:layout_alignParentLeft="boolean": Căn trái phần tử hiện tại theo phần tử cha nếu set là true.

Ta có ví dụ sau:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:background="#2c8291"
        android:layout_width="200dp"
        android:layout_height="200dp"/>

</RelativeLayout>

Các bạn hãy thử thêm các thuộc tính trên để xem kết quả như thế nào. Giả sử tôi thêm như sau:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_alignParentRight="true"
        android:background="#2c8291"
        android:layout_width="200dp"
        android:layout_height="200dp"/>

</RelativeLayout>

Và kết quả view sẽ được căn phải.

stdio_relative_layout_3

Ưu điểm

+ Thiết kế được các giao diện phức tạp.

Nhược điểm

+ Thời gian tính toán và layout các view con khá lâu so với FrameLayout.

+ Muốn sử dụng các thuộc tính như  android:layout_above, android:layout_toLeftOf thì phải đặt id cho các view mà view hiện tại xác định vị trí tương đối đối với các view đó.

LinearLayout

LinearLayout ViewGroup được sử dụng khá phổ biến cùng FrameLayout RelativeLayout,  LinearLayout được định nghĩa trong xml bởi cặp thẻ đóng mở <LinearLayout> và thẻ đóng </LinearLayout>.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   <!--View child-->

</LinearLayout>

Điểm đặc biệt của LinearLayout là có thể chia layout theo các tỉ lệ khác nhau. Chúng ta sẽ cùng nhau tìm hiểu ở dưới đây.

Quy tắc layout

LinearLayout sắp sếp các view con theo hai hướng:

- Vertical: Sắp sếp view con theo chiều dọc.

stdio_linear_layout_1

Các view được thêm vào sau sẽ được sắp xếp theo hướng mũi tên từ trên xuống, và không có view nào sẽ nằm đè lên view nào.

- Horizontal: Sắp sếp các view con theo chiều ngang:

stdio_linear_layout_2

Các view được thêm vào sau sẽ được sắp xếp theo hướng mũi tên từ trái qua phải, và không có view nào sẽ nằm đè lên view nào.

Để xác định hướng mà LinearLayout sẽ layout các phần tử con theo vertical hay horizontal ta sử dụng thuộc tính sau:

+ Vertical:

android:orientation="vertical"

+ Horizontal:

android:orientation="horizontal"

Ví dụ với đoạn mã

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <View
        android:background="#af5656"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <View
        android:background="#28c6a6"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    <View
        android:background="#7c145f"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    <View
        android:background="#c18936"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    <View
        android:background="#062001"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    
</LinearLayout>

Chúng ta sẽ có giao diện như sau:

stdio_linear_layout_3

Và với đoạn mã tiếp theo thay đổi orientation thành horizontal:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <View
        android:background="#af5656"
        android:layout_width="50dp"
        android:layout_height="match_parent" />

    <View
        android:background="#28c6a6"
        android:layout_width="50dp"
        android:layout_height="match_parent" />
    <View
        android:background="#7c145f"
        android:layout_width="50dp"
        android:layout_height="match_parent" />
    <View
        android:background="#062001"
        android:layout_width="50dp"
        android:layout_height="match_parent" />

</LinearLayout>

Ta có kết quả như dưới đây: 

stdio_linear_layout_4

Ví dụ thiết kế giao diện đăng nhập như sau:

stdio_linear_layout_5

Chúng ta dễ dàng thiết kế bằng xml với LinearLayout như sau:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <TextView
        android:text="username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:text="password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"/>

    <EditText
        android:inputType="textPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:text="login"
        android:layout_gravity="center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


</LinearLayout>

Chia tỉ lệ layout

Ngoài cách sử dụng LinearLayout thông thường thì LinearLayout thường được sử dụng để phân chia tỉ lệ layout bằng cách sử dụng thuộc tính layout_weight và weightSum.

  • weightSum: Xác định trọng số của LinearLayout hiện tại.
  • layout_weight: Trọng số mà view con trong LinearLayout chiếm giữ.

Chia tỉ lệ không có weightSum

Cùng xem xét đoạn mã sau:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <View
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="#af5656" />

    <View
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:background="#28c6a6" />

    <View
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:background="#7c145f" />
</LinearLayout>

Chúng ta không đánh trọng số weightSum cho LinearLayout nên tổng trọng số của nó sẽ được tính bằng tổng trọng số (layout_weight) các view con cộng lại.

Ở đây weightSum = 1 + 4 + 2 = 7. Có nghĩa là LinearLayout này sẽ chia thành 7 phần:

View thứ nhất có android:layout_weight="1" sẽ chiếm 1/7.

View thứ hai có android:layout_weight="4" sẽ chiếm 4/7.

View thứ ba có android:layout_weight="2" sẽ chiếm 2/7.

Kết quả chúng ta thấy như sau

stdio_linear_layout_6

Nếu orientation vertical

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#af5656" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4"
        android:background="#28c6a6" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:background="#7c145f" />
</LinearLayout>

Và kết quả

stdio_linear_layout_7

Chia tỉ lệ có weightSum

Khác với cách trên weightSum được tính toán bằng tổng của cách layout_weight của các view con. Thì chúng ta có thể xác định giá trị weightSum cụ thể như sau:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="10"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:background="#af5656" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:background="#28c6a6" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#7c145f" />
</LinearLayout>

Chúng ta xác định weightSum có giá trị là 10, có nghĩa là chia layout này thành 10 phần

View thứ nhất chiếm 5/10

View thứ hai chiếm 2/10

View thứ ba chiếm 1/10

Còn lại 2/10 sẽ là khoảng trắng.

stdio_linear_layout_8

Điểm khác biệt của hai cách chia tỉ lệ này là chia tỉ lệ không có weightSum thì các view trong LinearLayout sẽ "phủ" đầy layout này (không có khoảng trống). Còn với cách chia tỉ lệ có weightSum có thể sinh ra khoảng trống như ví dụ ở trên.

Ưu điểm

+ Thiết kế được các giao diện phức tạp.

+ Chia tỉ lệ layout, phù hợp với việc phát triển UI trên nhiều device có kích thước màn hình khác nhau.

Nhược điểm

+ Thời gian tính toán và layout view con tốn chi phí hơn so với FrameLayout và RelativeLayout. Đây là ViewGroup tính toán phức tạp nhất trong bộ ba ViewGroup thường xuyên được sử dụng (FrameLayout, RelativeLayout, LinearLayout).

Lời kết

Qua bài viết này tôi đã hướng dẫn các bạn ba ViewGroup thường xuyên sử dụng (FrameLayout, RelativeLayout, LinearLayout) trong quá trình thiết kế UI cho ứng dụng android. Tùy thuộc vào từng UI đơn giản hay phức tạp mà chúng ta sử dụng ViewGroup nào cho phù hợp, với những UI nào đơn giản thì chúng ta có thể sử dụng FrameLayout vì thời gian tính toán và layout của nó nhanh nhất, UI quá phức tạp thì chúng ta có thể kết hợp tất cả những ViewGroup trên. Nếu có thắc mắc nào các bạn hãy để lại câu hỏi ở phía dưới bài viết, tôi sẽ giải đáp thắc mắc cho các bạn.

 

 

THẢO LUẬN
ĐÓNG