Android Layout là một cách bố trí, sắp xếp các thành phần giao diệnxuất hiện trên màn hình. Bất cứ thành phần nào, một View (hoặc thừa kế từ View) đều có thể là con của một Layout. Tất cả các lớp Layout đều mở rộng từ lớp ViewGroup (mà kế thừa từ View), do đó bạn cũng có thể tạo một lớp Layout tùy biến của mình, bằng cách tạo một lớp mở rộng từ ViewGroup.
Một layout là một dạng của ViewGroup nên cũng là lớp con của lớp ViewGroup. Nhiệm vụ của layout là kiểm soát cách thức các thành phần hay views được hiển thị
trên màn hình giao diện.
Android SDK cung cấp một số layouts hỗ trợ cho việc thiết kế giao diện gồm:
o ConstraintLayout: Bố cục giao diện với các ràng buộc
o LinearLayout: Bố cục dạng tuyến tính
o TableLayout: Bố cục dạng bảng
o FrameLayout: Bố cục đơn
o GridLayout: Bố cục dạng lưới
o RelativeLayout: Bố cục dạng quan hệ
Ngoài ra, còn một số bố cục khác kế thừa từ ViewGroupnhư: ListView, Gridview, DrawerLayout, SlidingDrawer, ViewPager,…
3.3.1. FrameLayout
FrameLayout là một bố cục dạng khung đơn. Sử dụng trong các trường hợp xây dựng bố cục tổ chức hiển thị một đối tượng duy nhất.
ThS. Bùi Trung Úy 61
Frame Layout có thể chứa nhiều view và các view này có thể sắp chồng lên nhau. Và các view nằm dưới có thể bị các view nằm trên che khuất. Vì vậy, thường Frame Layout chỉ chứa một view. Ví dụ khai báo: <?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”" > <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android" /> </FrameLayout>
Ví dụ 2: FrameLayout chứa các view chồng lên nhau. Để định vị trí các view có thể dùng thuộc tính „android:layout_gravity‟ của đối tượng.
<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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" tools:context=".LayoutActivity"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/halong_bay" tools:layout_height="match_parent" tools:scaleType="centerCrop" android:contentDescription="Halong" />
ThS. Bùi Trung Úy 62
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="HaLong Bay, Viet Nam" android:textSize="24sp" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:text="Photo of HaLong" android:textSize="30sp" /> </FrameLayout>
Lưu ý: Trên cửa sổ „Design’ để chuyển sang bố cục FrameLayout hay bố cục nào khác bạn thực hiện như sau: Nhấp phải chuột trên layout và chọn „Convert View…‟
3.3.2. Linear Layout
LinearLayout là một bố cục tuyến tính sắp xếp các views con theo một hướng nhất
định theo chiều ngang (horizontal - từ trái qua phải) hoặc theo chiều dọc (vertical - từ
trên xuống dưới). Thuộc tính weight của Layout sẽ cho phép điều chỉnh kích thước của các view dựa trên mối quan hệ của chúng (cùng một hàng chẳng hạn).
ThS. Bùi Trung Úy 63 Đây được xem là dạng layout dễ sử dụng nhất, chúng cho phép chúng ta tạo ra các giao diện đơn giản bằng cách sắp xếp các view theo một chiều nhất định. Tuy nhiên,
vì đơn giản nên khi cần thiết kế các giao diện phức tạp, LinearLayout thường được sử
dụng trong một layout khác.
Ví dụ, 3 thiết kế giao diện dạng LinearLayout
Giao diện dạng vertical (ở giữa) có mã XML là: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent” android:orientation=“vertical” > <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/trang" /> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/den" /> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/xam" /> </LinearLayout>
Với các view là con nằm trong LinearLayout có một thuộc tính quan trọng
android:layout_weight, thuộc tính này gán một giá trị "trọng số" cho view, nó chỉ định có bao nhiêu không gian mà view này sẽ chiếm trên màn hình. Một giá trị trọng số
(weight) lớn hơn cho phép nó mở rộng để điền vào bất kỳ không gian còn lại trong view cha. View con có thể chỉ định một giá trị trọng số, và sau đó bất kỳ không gian còn lại trong ViewGroup sẽ được gán cho các View con khác theo tỷ lệ trọng số kê khai của chúng.
ThS. Bùi Trung Úy 64
3.3.3. Relative Layout
RelativeLayout là một dạng bố cục sắp xếp hiển thị các view con ở các vị trí tương đối. Vị trí của mỗi view có thểđược quy định liên quan đến các view khác liên quan
(như bên trái của hoặc bên dưới một view khác) hoặc ở các vị trí tương đối với khu vực cha (chẳng hạn như sắp xếp ngay phía dưới, bên trái hoặc trung tâm).
Các view khi được đặt lên layout này sẽ có vị trí phụ thuộc vào view đã đặt quan hệ, do đó khi thay đổi vị trí của một view sẽlàm thay đổi vị trí của các view còn lại.
RelativeLayout là một layout rất mạnh mẽ cho thiết kế một giao diện người dùng vì nó có thể loại bỏ các nhóm view lồng nhau và giữ cho hệ thống phân cấp bố trí thuận tiện hơn, đồng thời cải thiện hiệu suất.
ThS. Bùi Trung Úy 65 Mã thiết kế XML của giao diện sẽ là: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context=".LayoutActivity"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginStart="122dp" android:layout_marginLeft="122dp" android:layout_marginTop="88dp" android:text="Button 01" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button1" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_marginStart="168dp" android:layout_marginLeft="168dp" android:layout_marginTop="103dp" android:text="Button 02" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button2" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_marginStart="195dp" android:layout_marginLeft="195dp" android:layout_marginTop="97dp" android:text="Button 03" /> </RelativeLayout>
ThS. Bùi Trung Úy 66
3.3.4. TableLayout
TableLayout là một bố cục dạng bảng sắp xếp và hiển thị các views con theo các hàng và các cột, mỗi thành phần view con sẽ được đặt trong một ô. Một bảng có thể
có các ô trống và có thể thể bắc qua (span) nhau theo chiều ngang.
TableRow
Các đối tượng TableRow là các view con của một TableLayout (mỗi TableRow
định nghĩa một hàng trong bảng). Mỗi hàng có thể có nhiều ô, mỗi ô trong số đó có
thể chứa bất kỳ loại view nào. Vì vậy, một hàng có thể chứa một vài loại view, như
ImageView hoặc TextView. Một ô cũng có thể chứa ViewGroup (ví dụ, bạn có thể
cho một TableLayout khác vào trong một ô).
Các ô có thể bắc qua (span) nhau theo chiều ngang, nhưng không thể theo chiều thẳng đứng. Có thể có những ô không chứa view con nào. Bạn có thể sét đặt kích
thước cụ thể cho các view trong ô, nó sẽ làm thay đổi kích thước của ô Ví dụ với thiết kếnhư hình trên ta có mã XML là:
<?xml version="1.0" encoding="utf-8"?> <TableLayout 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"> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="136dp" android:layout_height="wrap_content" android:text="New" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="EDIT" />
ThS. Bùi Trung Úy 67 <Button android:id="@+id/button5" android:layout_width="105dp" android:layout_height="wrap_content" android:text="Open" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button3" android:layout_width="139dp" android:layout_height="wrap_content" android:layout_span="2" android:text="New Button" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button4" android:layout_width="93dp" android:layout_height="wrap_content" android:layout_span="3" android:text="New Button 2" /> </TableRow> </TableLayout> 3.4.5. ConstraintLayout
ConstraintLayout là một layout mạnh và được khuyến khích dùng để tạo giao diện, vì nó giúp tạo ra các giao diện phức tạp, mềm dẻo (hạn chế tối đa sử dụng các layout lồng nhau). Nó giúp định vị, sắp xếp các view con dựa trên sự ràng buộc liên hệ của
các view con với view cha và sự liên hệ ràng buộc giữa các view con với nhau, với cơ chế tạo xích các view, gán trọng số hay sử dụng trợ giúp giao diện kéo thả.
ConstraintLayout thuộc thư viện hỗ trợ nên để tích hợp vào dự án hãy thêm vào Gradle phiên bản muốn dùng, ví dụ:
implementation 'com.android.support:appcompat-v7:27.1.0'
ThS. Bùi Trung Úy 68
Sự ràng buộc
Mỗi view trong ConstraintLayout để định vị được chính xác cần tối thiếu 2 ràng buộc, một theo phương ngang (X) và một theo phươngđứng (Y).
Khái niệm ràng buộc giữa các phần tử ở đây ám chỉ sự liên kết với nhau của các phần tử với nhau (kể cả với phần tử cha ConstraintLayout), sự căn chỉnh phần tử theo phần tử khác, hoặc với những đường thẳng ẩn thêm vào. Mỗi ràng buộc của phần tử
view sẽ ảnh hưởng đến vị trí của nó theo trục X hoặc trục Y. Các View không có ràng buộc sẽ định vị ở góc trái - trên (tọa độ 0,0).
Trước tiên tham khảo bảng các thuộc tính về ràng buộc layout_constraint ... , các thuộc tính này sử dụng namespacelà „app‟ và giá trị nó gán vào là một ID của phần tử khác để kết nối ràng buộc hoặc là phần tử cha bằng hằng số "parent", ví dụ:
app:layout_constraintBottom_toBottomOf="parent"
Một số các thuộc tính ràng buộc:
layout_constraintLeft_toLeftOf: Ràng buộc cạnh trái của phần tử tới phần tử chỉ ra trong giá trị (gán ID).
layout_constraintLeft_toRightOf: Ràng buộc bên trái với bên phải của phần tử.
layout_constraintRight_toLeftOf: Ràng buộc bên phải với bên trái.
layout_constraintRight_toRightOf: Ràng buộc phải với phải.
layout_constraintTop_toTopOf: Ràng buộc cạnh trên với cạnh trên.
layout_constraintTop_toBottomOf: Ràng buộc cạnh trên nối với cạnh dưới.
layout_constraintBottom_toTopOf: Ràng buộc dưới với trên.
layout_constraintBottom_toBottomOf: Ràng buộc dưới với dưới
ThS. Bùi Trung Úy 69 Trên màn hình Design, để tạo ràng buộc cho view, bạn hãy nhấn giữ chuột vào
chấm tròn ở mỗi cạnh của view và kéo đến chấm tròn của một view nào đó hay đến biên của màn hình, bạn sẽ thấy các view sẽ “kết nối” với nhau sau khi bạn tạo ràng
buộc theo cách này.
Phần tử Guideline
Là một đường kẻ bí ẩn (hidden) trong ConstraintLayout nằm ngang hoặc đứng nó như là một view con để các view khác ràng buộc đến nếu muốn. Mã XML thêm vào:
<android.support.constraint.Guideline android:id="@+id/guideline_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.3" />
Thiết lập đó là đường kẻ đứng bằng thuộc tính android:orientation="vertical " hoặc đường kẻngang với thuộc tính android:orientation="horizontal ".
Vị trí của Guideline có thể thiết lập nó cách cạnh trái (hoặc trên nếu là Guideline ngang) bằng thuộc tính app:layout_constraintGuide_percent giá trị là tỷ lệ phần trăm như 0.3 (30%), 0.5 (50%) độ rộng (cao) của ConstraintLayout, hoặc bằng thuộc tính xác định kích thước như: app:layout_constraintGuide_begin="130dp".
ThS. Bùi Trung Úy 70
Mã XML của giao diện trên là:
<?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" tools:context=".LayoutActivity"> <android.support.constraint.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.3" /> <android.support.constraint.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_begin="130dp" /> </android.support.constraint.ConstraintLayout>
Tạo ràng buộc Baseline cho view
Nếu như sự ràng buộc cho view ở trên là các điểm neo giữa các view. Thì Baseline
là sự canh chỉnh các text bên trong một view với nhau. Việc canh chỉnh dựa trên baseline rất thích hợp cho các phần tửnhư TextView, EditText hay Button.
Để tạo rảng buộc baseline, bạn hãy nhấn vào một view, rồi nhấn vào nút có ký hiệu - Nút tạo baseline xuất hiện bên dưới view đó. Baseline sẽ xuất hiện ngay dưới text của phần tử, kéo baseline của view này vào baseline của view khác để tạo ra một ràng buộc baseline y như ràng buộc giữa các view vậy. Việc xóa baseline cũng không khác với xóa ràng buộcbình thường.
ThS. Bùi Trung Úy 71
Các thể hiện của view trên cửa sổ thuộc tính:
(1) ID: Khi một view được tạo ra bên trong layout sẽ có một ID mặc định như hình
trên, và bạn có thể thay đổi giá trị của ID này.
(2) Margin: Margin chính là khoảng cách của một view đến các view khác. Bạn có thể chỉ định giá trị margin mặc định cho tất cả các view bên trong ứng dụng của
mình bằng cách thay đổi số này ở toolbar.
(3) Các loại khoảng cáchràng buộc:
Fixed: Nếu bạn chỉ định khoảng cách kiểu này, bạn có thể điền giá trị ở các thuộc tính layout_widthvà layout_heighttheo kích thước cố định.
Match_constraint: Mở rộng kích thước view chiếmlấy hết không gian ở view cha. Về cơ bản, nó chiếm không gian của view cha nhưng chiếm trong khả năng cho phép, tức là vẫn tuân thủ theo quy luật mà các constraint đã tạo, vẫn chừa không gian cho các view khác xuất hiện. Nếu bạn chỉ định kiểu khoảng cách là
match_constraintthì mã XML của view sẽ mang giá trị là 0dp.
Wrap_content: mở rộng kích thước view theo nội dung bên trong nó. (4) Giá trị Bias: khi bạn đã tạo các điểm neo constraint đối xứng, như trái-phải hoặc trên-dưới thì các đường constraint sẽ như một liên kết lò xo, mặc định nó có độ mềm (độ cứng) bằng nhau dẫn đến view sẽ nằm giữa 2 điểm neo của ràng buộc. Thanh bias ngang hoặc dọc sẽ xuất hiện, bạn có thể “nắm” lấy hình tròn ở thanh này và dịch chuyển để tạo hiệu ứng xê dịch view theo trọng số. Con số mà hình tròn trên
thanh bias hiển thị chính là phần trăm của trọng số.
Nếu muốn điều chỉnh Bias trong mã XML bạn có thể sử dụng thuộc tính:
app:layout_constraintVertical_bias: độ mềm của ràng buộc theo chiều ngang. Với tổng độ mềm là 1 thì khi app:layout_constraintVertical_bias="0.1" có nghĩa độ mềm ràng buộc thứ hai sẽ là 0.9.
ThS. Bùi Trung Úy 72
3.5. Xử lý sự kiện trên giao diện
Mỗi view trong Android có thể sinh ra các sự kiện khi người dùng tác động lên nó. Chẳng hạn, một nút trên giao diện sẽ sinh ra sự kiện onClick khi người dùng bấm vào, EditText sinh ra sự kiện onFocusChange khi nhận focus (người dùng bấm vào ô nhập liệu này) hoặc mất focus (khi view khác nhận focus). Tuy nhiên, để nắm bắt và xử lý các sự kiện này, bạn phải đăng ký trình lắng nghe sự kiện (event listener) và cài đặt các xử lý tương ứng với hành động của người dùng trên lớp view cụ thể mà người dùng tương tác.
Trong những lớp view dùng để tạo giao diện, khi đăng ký một trình lắng nghe sự kiện, có vài phương thức callback dạng public hỗ trợ cho các sự kiện giao diện người dùng, các phương thức này còn được gọi trình xử lý sự kiện (event handler) sẽ được framework Android gọi khi có hành động tương ứng xảy ra trên đối tượng đó.
Một số sự kiện thường dùng:
Event listener Event handler Giải thích
View.OnClickListener onClick() Sự kiện khi người dùng click hoặc chạm (touch) hoặc focus trên bất kỳ đối tượng view như button, text, image,...
View.OnLongClickListener onLongClick() Tương tự sự kiên trên, nhưng nhấn và giữlâu hơn. View.OnFocusChangeListener onFocusChange() Sự kiện phát sinh khi đối
tượng view nhận hoặc mất focus.
onKey() Sự kiện phát sinh khi người dùng focus trên đối tượng và nhấn (press) hoặc thả (release) một phím.
View.OnTouchListener onTouch() Sự kiện phát sinh khi người dùng vuốt, chạm, hoặc di chuyển trên màn hình cảm ứng.
View.OnMenuItemClickListener onMenuItemClick() Sự kiện phát sinh khi người dùng chọn một mục trong menu View.OnCreateContextMenuIte mListener onCreateContextMen u()
Sự kiện phát sinh khi người dùng chọn một mục trong menu ngữ cảnh
ThS. Bùi Trung Úy 73
Các cách đăng ký sự kiện
Cách 1: Cài đặt giao tiếp trình lắng nghe sự kiện trong Activity <My Activity> implements <Event_Listener>
Sau đó cài đặt tất cảcác phương thức trong interface này.
Ví dụ: Ta có màn hình giao diện gồm một nút nhấn và một textview. Ta sẽ viết code khi người dùng click vào button này, ứng dụng sẽ thay đổi text của TextView thành “Xin chào bạn!”.
Trong khai báo lớp Activity, ta cài đặt interface xử lý sự kiện là OnClickListener bằng cách bổsung đoạn code:
public class MainActivity extend AppCompatActivity implements View.OnClickListener Khi thêm vào đoan code này Android Studio sẽ thông báo lỗi như hình dưới, nguyên nhân là do chưa khai báo hàm xử lý sự kiện cho chúng. Bạn click vào biểu tượng bóng đèn -> chọn „Implement methods‟ -> chọn OK.
Chọn phương thức onClick() từ hộp thoại:
ThS. Bùi Trung Úy 74 Code hoàn chỉnh: import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener { TextView tvDemo;
Button btnChange; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvDemo = (TextView) findViewById(R.id.tvDemo); btnChange = (Button) findViewById(R.id.button);
//Đăng ký sự kiện cho Button
btnChange.setOnClickListener(this); }
@Override
public void onClick(View v) {
tvDemo.setText("Xin chào bạn!");
} }
Cách 2: Sử dụng lớp nội (inner class) Cách dùng:
<Đối tượng>.set<Event_Listener>(new View.<Event_Listener>() { @Override
public void <Event_Handler>(View v) { //Cài đặt xử lý sự kiện
} });
Ví dụ trên ta có thể viết lại như sau, trong hàm onCreate() của Activity: btnChange = (Button) findViewById(R.id. btnChange);
ThS. Bùi Trung Úy 75
btnChange.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) { tvDemo.setText("Xin chào bạn!"); }
});
Hoặc bạn có thể sử dụng tương tự với “lớp ẩn danh” (anonymous class) như sau: //---create an anonymous class to act as a button click listener---
View.OnClickListener btnListener = new View.OnClickListener() {
public void onClick(View v) { tvDemo.setText("Xin chào bạn!"); }
};
btnChange = (Button)findViewById(R.id.btnChange); btnChange.setOnClickListener(btnListener);
Cách 3: Tạo hàm xử lý sự kiện và thiết lập thuộc tính onClick của đối tượng là hàm vừa tạo:
Cú pháp khai báo:
public void <methodName>(View v) { //Viết xử lý sự kiện