Để 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ừ View là ViewGroup, 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 |