Để tạo ra một giao diện người dùng trong ứng dụng Android, chúng ta xây dựng từ sự kết hợp các thành phần có tên là View, ViewGroup, Layout.

View và ViewGroup

Các thành phần giao diện xây dựng từ lớp cơ sở View (android.view.View) của Android, các thành phần này cung cấp sẵn khá đa dạng như Button, TextView, CheckBox ... tất cả chúng ta gọi nó là View. Sơ đồ các View được mộ tả theo sơ đồ như hình dưới.

View biểu diễn một hình chữ nhật, trong đó nó hiện thị thông tin nào đó cho người dùng, và người dùng có thể tương tác với View. Nhưng loại View cơ bản cần tìm hiểu trước tiên đó là: TextView, ImageView, Button, ImageButton, EditText

ViewGroup

Trong sơ đồ trên, có một lớp abstract kế thừa từ ViewViewGroup, nó cũng chính là một View nhưng có khả năng chứa các View khác bên trong (kể cả ViewGroup khác). Nó là lớp cơ sở để xây dựng nên các layout như trên sơ đồ ta thấy có: ConstraintLayout, RelativeLayout, LinearLayout, GridLayout, FrameLayout

Android có sẵn nhiều loại View, ViewGroup, Layout mà ở chế độ Design bạn có thể kéo thả để xây dựng giao diện

Các ViewGroup cũng như một phần tử chứa các View con, và ViewGroup cũng được kế thừa trở thành phần gốc để xây dựng UI gọi là layout

Các layout

Các layout chính là các View (cụ thể nó kế thừa thừa ViewGroup) được thiết kế với mục đích chứa các View con và điều khiển, sắp xếp vị trí các View con đó trên màn hình, mỗi layout có cơ chế điều khiển vị trí View con riêng của mình. Có một số layout mà bạn tham khảo trước tiên như:

  • FrameLayout đơn giản chỉ cung cấp một vùng màn hình, thường dùng nó để hiện thị một View con duy nhất. Nếu đặt vào nó nhiều view con, thì mặc định các view con này sẽ xếp chồng lên nhau. Tuy vậy, vị trí các view con trong nó cũng có thể điều chỉnh thông qua giá trị tham số gravity, ví dụ trong view con thiết lập android:layout_gravity="bottom|right" thì view con nằm về phía trái, dưới của FrameLayout
  • ConstraintLayout (giới thiệu trong Android 7), sử dụng layout này được khuyến khích cho hầu hết trường hợp. ConstraintLayout cho phép điều khiển vị trí và ứng sử của các view con trong layout bằng cách gán dàng buộc đơn giản vảo mỗi view con. Từ đó mà một bố cục phức tạp có thể dễ dàng được tạo ra mà sử dụng ít nhất sự lồng nhau trong layout (layout này nằm trong layout khác) giúp cho cải thiện tốc độ. ConstraintLayout cũng tích hợp sẵn vào Android Studio Layout Editor nên bạn có thể điều chỉnh một cách trực quan các View con trong layout này.
  • LinearLayout với layout này thì các view con được xếp nối tiếp nhau (linear) thành một hàng hay một cột (tùy vào lúc thiết kế thiết lập hướng xếp). Có một giá trị weight có thể gán vào mỗi View con để cho biết View con đó chiếm bao nhiêu không gian trong một tỷ lệ tương quan với các View con khác.
  • RelativeLayout chô phép các view con định vị căn vào liên hệ với các view con khác đồng thời liên hệ với view cha thông qua các tham số align và margin. Ví dụ một View con thiết lập nằm ở giữa RelateiveLayout (android:layout_centerInParent="true"), một View con khác có thể thiết lập nằm căn trùng lề phải với View này (android:layout_alignRight="@+id/other")
  • GridLayout chia ra thành lưới gồm một số hàng và một số cột để chứa các view con.
  • TableLayout cung cấp khả năng bố trí các view con thành một lưới dạng bảng (gồm có hàng và cột). Một dòng của bảng biểu diễn bàng đối tượng view con TableRow, trong nó chứa có phần tử View con hiểu như các ô bảng.
  • CoordinatorLayout nó được thiết kế nhằm mục đích có sự tương tác của các View con trong nó, đặc biệt sử dụng với ActionBar, FloatingActionButton, Snackbar ...

Tạo View bằng Code Java và XML

Code Java

Các đối tượng View như TextView, Button, ImageView, LinearLayout, ConstraintLayout ... đều có thể tạo ra và thiết lập bằng các dòng mã Java thông thường. Các đối tượng tạo ra bằng toán tử new với phương thức khởi tạo View có tham số là một Context (Activity là một Context). Ví dụ như trong OnCreate có tạo đối tượng TextView:

TextView textview = new TextView(this);

Một số tính chất / phương thức có trong tất cả các View

Các View dù là Button, FrameLayout ... đều là kế thừa từ View, nên nó có các phương thức chung mà bạn có thể dùng tới như:

ID của View

Mỗi View bạn có thể gán cho nó một định danh là một số nguyên bằng phương thức setId(int), ID dùng để tìm ra View trong hệ thống giao diện bằng phương thức findViewById(id) của Activity hoặc của một ViewGroup nào đó.

int textview_id = 100;
//...
textview.setId(textview_id);

Thiết lập padding

Hình vuông trình bày nội dung của View cách các cạnh thật của View một khoảng thiết lập bởi padding. Sử dụng phương thức: setPadding(int left, int top, int right, int bottom)

Thiết lập LayoutParams

Khi thiết kế một giao diện, thường bạn tạo ra nhiều các View, trong đó có cấu trúc một View cha chứa các View con ở bên trong. Như View cha là đối tượng ConstraintLayout, bên trong nó chứa các View con là các đối tượng như TextView, Button ... Các View con xuất hiện ở vị trí như thế nào trong View cha được thiết lập qua thông số là đối tượng LayoutParams, để thiết lập LayoutParams cho một View sử dụng phương thức: setLayoutParams(LayoutParams). Ví dụ:

ConstraintLayout.LayoutParams txtParams
        = new ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT );
//...
textview.setLayoutParams(txtParams);

Chú ý là tùy thuộc View cha là loại ViewGroup nào (LinearLayout, FrameLayout, CoordinatorLayout, RelativeLayout, ConstraintLayout ... ) mà tham số của setLayoutParams(), cần khởi tạo từ lớp LayoutParams của ViewGroup đó (LinearLayout.LayoutParams, FrameLayout.LayoutParams, CoordinatorLayout.LayoutParams ... )

Như đoạn code phía trên, textview sẽ đặt là phần tử con trong một phần tử cha lớp ConstraintLayout, nên ta khởi tạo một đối tượng lớp ConstraintLayout.LayoutParams

Khởi tạo LayoutParams

Mặc dù có nhiều lớp LayoutParams như LinearLayout.LayoutParams, FrameLayout.LayoutParams ... nhưng nó đều kế thừa từ ViewGroup.LayoutParams nên tạo đối tượng với toán tử new tham số khởi tạo là chiều rộng và chiều cao của phần tử (tính theo pixel). Ví dụ:

int chieurong = 100; //pixel
//Nếu gán chieurong = ViewGroup.LayoutParams.MATCH_PARENT (-1) chiều rộng bằng phần tử cha
//Nếu gán chieurong = ViewGroup.LayoutParams.WRAP_CONTENT (-2) chiều rộng tự co theo nội dung

int chieucao  = 100; //pixel
//Nếu gán chieucao = ViewGroup.LayoutParams.MATCH_PARENT (-1) chiều cao bằng phần tử cha
//Nếu gán chieucao = ViewGroup.LayoutParams.WRAP_CONTENT (-2) chiều cao tự co theo nội dung


ConstraintLayout.LayoutParams txtParams
        = new ConstraintLayout.LayoutParams(chieurong, chieucao);

Ví dụ sau tạo ra một giao diện hoàn toàn viết bằng code Java

import static android.support.constraint.ConstraintLayout.LayoutParams.PARENT_ID;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        int pixel_8_dp   = DpToPx(10);
        int textview_id  = 100;
        int imageview_id = 101;
        int edittext_id  = 102;
        int button_id    = 103;
        int image_button    = 104;

        //Sử dụng ConstraintLayout làm View cha chứa các View con
        ConstraintLayout parentView = new ConstraintLayout(this);
        parentView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT,
                                                              MATCH_PARENT));

        //Tạo và thiết lập View con là một TextView
        TextView textview = new TextView(this);
        textview.setText("Đây là một TextView");
        textview.setId(textview_id);
        textview.setPadding(pixel_8_dp, pixel_8_dp, pixel_8_dp, pixel_8_dp);

        ConstraintLayout.LayoutParams txtParams
                = new ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT );
        txtParams.setMarginEnd(pixel_8_dp);
        txtParams.setMarginStart(pixel_8_dp);
        txtParams.setMarginStart(pixel_8_dp);
        txtParams.endToEnd        = PARENT_ID;
        txtParams.startToStart    = PARENT_ID;
        txtParams.topToTop        = PARENT_ID;
        textview.setLayoutParams(txtParams);

        //Tạo và thiết lập View con là một ImageView
        ImageView imageView = new ImageView(this);
        imageView.setId(imageview_id);
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.nature);
        imageView.setImageBitmap(bm);
        imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        ConstraintLayout.LayoutParams imageParam
                = new ConstraintLayout.LayoutParams(DpToPx(200), DpToPx(150));
        imageParam.topToBottom = textview_id;
        imageParam.endToEnd        = PARENT_ID;
        imageParam.startToStart    = PARENT_ID;
        imageView.setLayoutParams(imageParam);




        //Tạo và thiết lập View con là một EditText
        EditText edittext = new EditText(this);
        edittext.setId(edittext_id);
        edittext.setText("Nhập dòng chữ");
        ConstraintLayout.LayoutParams edittextParam
                = new ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
        edittextParam.topToBottom = imageview_id;
        edittextParam.endToEnd = PARENT_ID;
        edittextParam.startToStart = PARENT_ID;
        edittext.setLayoutParams(edittextParam);

        //Tạo và thiết lập View con là một Button
        Button button = new Button(this);
        button.setId(button_id);
        button.setText("Button");
        ConstraintLayout.LayoutParams buttonParam
                = new ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
        buttonParam.topToBottom = edittext_id;
        buttonParam.endToEnd = PARENT_ID;
        buttonParam.startToStart = PARENT_ID;
        button.setLayoutParams(buttonParam);

        //Tạo và thiết lập View con là một ImageButton
        ImageButton imagebutton = new ImageButton(this);
        imagebutton.setId(image_button);
        imagebutton.setImageBitmap(bm);
        imagebutton.setScaleType(ImageView.ScaleType.FIT_CENTER);
        ConstraintLayout.LayoutParams imagebuttonParam
                = new ConstraintLayout.LayoutParams(DpToPx(100), DpToPx(50));
        imagebuttonParam.topToBottom = button_id;
        imagebuttonParam.endToEnd = PARENT_ID;
        imagebuttonParam.startToStart = PARENT_ID;
        imagebutton.setLayoutParams(imagebuttonParam);


        //Đưa các View con vào View cha
        parentView.addView(textview);
        parentView.addView(imageView);
        parentView.addView(edittext);
        parentView.addView(button);
        parentView.addView(imagebutton);

        setContentView(parentView);
    }

    //Convert DP to PX
    public int DpToPx(float dp) {
        int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                this.getResources().getDisplayMetrics());
        return px;
    }

}

Tạo View bằng XML

Thay vì dùng code Java tạo View trực tiếp, thực tế hầu hết các giao diện sẽ sử dụng các file XML, trong đó mô tả các thành phần của View (View cha, View con, mỗi liên hệ ...). XML này là một layout, một tài nguyên.

Từ file tài nguyên XML chứa giao diện, Android có cơ chế nạp và phân tích XML này đó là LayoutInflater, nó sẽ đọc nội dung XML, căn cứ vào nội dung nó sẽ tạo ra các hệ thống các View tương đương với cách viết code Java.

Ví dụ như trong onCreate của Activity mà phương thức setContentView(id) được gọi, nó sẽ tự động đọc nội dung XML của tài nguyên định danh theo id, căn cứ nội dung đọc được nó sẽ tạo ra View, rồi gán cho Activity như khi chúng ta goi setContentView(View).

Sử dụng XML để mô tả giao diện là thông dụng trong Android, ngoài ra file XML còn rất nhiều chức năng khác như lưu các tham số cấu hình ứng dụng, tạo ra các hình ảnh vector, mô tả các hoạt họa ...

Cơ bản về XML

XML (Extensible Markup Language - ngôn ngữ đánh dấu mở rộng), là một văn bản (Text) dùng để lưu dữ liệu. Các giá trị dữ liệu được lưu thành các phần tử. Mỗi phần tử sẽ bắt đầu bẳng một tag mở và kết thúc bằng tag đóng. Tag là thẻ phần tử, tên thẻ này do bạn đặt / hoặc do ứng dụng quy đinh. Ví dụ tên thẻ là TextView thì Inflate của Android đọc biết đây là bắt đầu mô tả một đối tượng TextView.

Ví dụ đây là một nút trong XML

<sanpham>Sony Xperia XZ1</sanpham>

Bạn thấy tên thẻ là sanpham, mở thẻ bằng <sanpham> và đóng thẻ bằng </sanpham>. Trong nút XML này chứa dữ liệu là dòng text: Sony Xperia XZ1

Tại thẻ mở <sanpham> bạn có thể cho thêm các thuộc tính, bằng cách đưa vào các cặp tenthuoctinh = "giatrithuoctinh"

<sanpham hedieuhanh="Android 7.0" ram="4GB">Sony Xperia XZ1</sanpham>

Như trên bạn vừa cho thêm thuộc tính về sản phẩm, nó mô tả thêm dữ liệu về sản phẩm, đó là chạy hệ điều hành nào, ram bao nhiêu

Tiếp theo là mỗi nút XML có thể chứa các nút dũ liệu con, tạo thành cấu trúc dạng cây (phần tử cha / phần tử con)

<dienthoai>
    <sanpham hedieuhanh="IOS 10" ram="4GB">Iphone 7</sanpham>
    <sanpham hedieuhanh="Android 7.0" ram="4GB">Sony Xperia XZ1</sanpham>
</dienthaoi>

Như vậy bạn thấy cấu trúc dữ liệu, nút dienthoai (nhóm sản phẩm) có 2 sản phẩm (sanpham)

Nếu như nút dữ liệu không chứa bên trong dữ liệu nút khác, thì có thể tự đóng thẻ luôn (không cần cặp thẻ đóng mở đầy đủ). Ví dụ:

<manhinh loai="LCD" dophangiai="Full HD" />

Những quy tắc này áp dụng để viết tài nguyên dang XML cho Android như các file layout, Manifest, Drawable ...

Ví dụ:

<android.support.constraint.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">
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:padding="10dp"
        android:text="Đây là một TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

Đọc XML trên, bạn thấy nút cha là phần tử android.support.constraint.ConstraintLayout với các thuộc tính như android:layout_width ... bên trong nó chứa một phần tử con là TextView với các thuộc tính như android:id, android:text ...

xmlns:android="http://schemas.android.com/apk/res/android" là thiết lập namspace, đây là thiết lập để tránh thuộc tính có thể trung nhau về tên. Với định nghĩa trên thì nó có namespace là android, thì thay vì viết thuộc tính layout_width="wrap_content" phải viết đầy đủ cả namespace thành android:layout_width="wrap_content" (làm điều này để phòng tránh xung đột về tên, ví dụ sau này bạn cũng muốn xây dựng một thuộc tính tên trùng layout_width thì bạn cần viết yournamespace:layout_width = "wrap_content")

Ví dụ chuyển Layout viết bằng code Java phần trên thành 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">


    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:padding="10dp"
        android:text="Đây là một TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="200dp"
        android:layout_height="150dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:src="@drawable/nature"
        android:scaleType="fitCenter"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textview" />

    <EditText
        android:id="@+id/editext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Nhập dòng chữ"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageview" />

    <Button
        android:id="@+id/button"
        app:layout_constraintTop_toBottomOf="@+id/editext"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:text="Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageButton
        android:id="@+id/imagebutton"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:scaleType="fitCenter"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        android:src="@drawable/nature" />
</android.support.constraint.ConstraintLayout>

Sau khi có layout viết theo XML, nạp vào Activity bằng phương thức: setContentView(id)

Hệ thống cấp bậc các View

Mỗi View trong hệ thống giao diễn được biểu diễn bởi một hình chữ nhật, nó vẽ trên nó thông tin gửi tới người dùng cũng như là nơi để nhận thông tin nhập vào như nhập dữ liệu, chạm, vuốt. Một giao diện người dùng được bắt đầu bằng một View gốc, bên trong nó chứa các View con, đến lượt các View con lại có thể chứa các View con khác ... Cứ như vậy, giao diện hình thành được biểu diễn như một đồ thị dạng cây nhiều nhánh (view hierarchy)

Tổng kết

Để xây dựng được một giao diện người dùng khi đã hiểu các thành phần ở trên có thể thực hiện dễ dàng và chuẩn mực hơn. Bạn có thể tạo ra một UI bằng nhiều cách: Sử dụng Android Studio Layout Editor với chức năng kéo thả, chỉnh sửa và hiện thị trực quản, có thể chỉnh sửa trực tiếp trên file XML biểu diễn giao diện, có thể viết code Java tạo ra cả một hệ thống Cấp bậc các View.

Tuy nhiên để mang lại hiệu suất của ứng dụng, thì khi tạo giao diện các phần tử càng ít lồng nhau càng tốt, càng ít ràng buộc càng tốt.

Một số thuộc tính XML trong tất cả các View

Các thuộc tính này có trong TẤT CẢ CÁC VIEW từ TextView, ImageButton, LinearLayout ...

Thuộc tính Diễn tả
android:alpha Nhận giá trị từ 0 - 1 là kênh alpha (độ trọng suốt) của View. Nhận 0 trong suốt hoàn toàn.
android:background Thiết lập nền bằng màu sắc, drawable ... cho View
android:backgroundTint Thiết lập màu sẽ nhuộm vào nền
android:clickable Thiết lập true thì View có khả năng nhận sự kiện click
android:foreground Gán một ảnh Drawable để vẽ vào phần nội dung View
android:foregroundGravity Định vị foreground trong View
android:id Gán một ID vào View, ví dụ android:id="@+id/imagebutton"
android:minWidth Gán kích thước chiều rộng nhỏ nhất
android:padding Thiết lập kích thước Padding cho cả 4 cạnh
android:paddingLeft Thiết lập Padding cạnh trái (Tương tự có android:paddingRight, android:paddingStart, android:paddingTop)
android:rotation Thiết lập góc xoay của View (độ)
android:scaleX Tỷ lệ thu/phong View theo chiều X
android:scaleY Tỷ lệ thu/phong View theo chiều Y
android:tag Gán một giá trị vào làm tag của View, sau này có thể đọc lại bằng getTag() và có thể tìm lại View bằng findViewWithTag()
android:theme Thiết lập theme cho View
android:visibility Gán các giá trị: gone (ẩn hoàn toàn), invisible (không hiện thị - nhưng chỗ nó chiếm vẫn còn), visible (hiện thị)
android:translationX Dịch chuyển view theo chiều X
android:translationY Dịch chuyển view theo chiều Y

Đăng ký nhận bài viết mới