Android cung cấp một mô hình được cơ cấu thành phần tinh vi và mạnh mẽ để xây dựng giao diện người dùng, dựa trên các lớp bố cục cơ bản View và ViewGroup. Nền tảng này bao gồm nhiều lớp con View và ViewGroup được tạo sẵn (lần lượt được gọi là tiện ích và bố cục) mà bạn có thể dùng để tạo giao diện người dùng.
Một phần danh sách các tiện ích widget có sẵn bao gồmButton,
TextView,
EditText,
ListView,
CheckBox,
RadioButton,
Gallery
Spinner,
cũng như AutoCompleteTextView với mục đích đặc biệt hơn,
ImageSwitcher, và
TextSwitcher.
Trong số các bố cục có sẵn gồm LinearLayout, FrameLayout, RelativeLayout và các bố cục khác. Để xem thêm ví dụ, hãy xem phần Các bố cục phổ biến.
Nếu không có tiện ích widget hoặc bố cục tạo sẵn nào đáp ứng được nhu cầu của bạn, thì bạn có thể tạo lớp con View của riêng mình. Nếu chỉ cần thực hiện một số điều chỉnh nhỏ đối với một tiện ích hoặc bố cục hiện có, bạn có thể tạo lớp con cho tiện ích hoặc bố cục đó và ghi đè các phương thức.
Việc tạo các lớp con View riêng sẽ giúp bạn kiểm soát chính xác hình dáng và chức năng của một phần tử màn hình. Để đưa ra ý tưởng về thành phần điều khiển có thể dùng với các thành phần hiển thị tuỳ chỉnh, bạn có thể tham khảo một số ví dụ về những hành động có thể thực hiện với các thành phần đó:
-
Bạn có thể tạo một kiểu
Viewđược kết xuất hoàn toàn tuỳ chỉnh, chẳng hạn như một núm "điều khiển âm lượng" được kết xuất bằng đồ hoạ 2D, giống như một thành phần điều khiển điện tử analog. -
Bạn có thể kết hợp một nhóm thành phần
Viewthành một thành phần duy nhất mới, có thể để tạo một thành phần như hộp kết hợp (kết hợp danh sách bật lên và trường văn bản nhập tự do), một chế độ kiểm soát bộ chọn hai ngăn (ngăn bên trái và bên phải có danh sách trong mỗi ngăn, nơi bạn có thể chỉ định lại mục nào nằm trong danh sách nào), v.v. -
Bạn có thể ghi đè cách thành phần
EditTextđược kết xuất trên màn hình. Ứng dụng mẫu NotePad sử dụng phương pháp này rất hiệu quả để tạo một trang ghi chú có dòng kẻ. - Bạn có thể ghi lại các sự kiện khác (chẳng hạn như thao tác nhấn phím) và xử lý các sự kiện đó theo một cách tuỳ chỉnh, chẳng hạn như chơi trò chơi.
Các phần sau đây giải thích cách tạo các thành phần hiển thị tuỳ chỉnh và cách sử dụng các thành phần đó trong ứng dụng của bạn. Để biết thông tin tham khảo chi tiết, hãy xem lớp View.
Phương pháp tiếp cận cơ bản
Sau đây là thông tin tổng quan cấp cao về những điều bạn cần biết để tạo các thành phần View của riêng mình:
-
Mở rộng một lớp
Viewhoặc lớp con hiện có bằng lớp của riêng bạn. -
Ghi đè một số phương thức từ lớp cha. Các phương thức của lớp cha cần ghi đè bắt đầu bằng
on, ví dụ:onDraw(),onMeasure()vàonKeyDown(). Điều này tương tự như các sự kiệnontrongActivityhoặcListActivitymà bạn ghi đè cho các vòng đời và các chức năng hook khác. - Sử dụng lớp mở rộng mới của bạn. Sau khi hoàn tất, bạn có thể sử dụng lớp mở rộng mới thay cho thành phần hiển thị mà lớp đó dựa trên.
Thành phần được tuỳ chỉnh đầy đủ
Bạn có thể tạo các thành phần đồ hoạ được tuỳ chỉnh đầy đủ và xuất hiện theo ý muốn. Có thể bạn muốn một đồng hồ VU dạng đồ hoạ trông giống như một thước đo analog cũ, hoặc một thành phần hiển thị văn bản dài để hát karaoke trong đó một quả bóng sẽ nẩy lên theo các từ khi bạn hát theo máy karaoke. Bạn có thể muốn một thành phần có những đặc điểm mà các thành phần tích hợp sẵn sẽ không làm được, bất kể có kết hợp bằng cách nào đi chăng nữa.
Rất may là bạn có thể tạo các thành phần có giao diện và hành vi theo ý muốn, chỉ bị giới hạn bởi trí tưởng tượng, kích thước màn hình và công suất xử lý hiện có. Hãy nhớ rằng ứng dụng của bạn có thể phải chạy trên một thiết bị có công suất thấp hơn đáng kể so với máy trạm trên máy tính.
Để tạo một thành phần tuỳ chỉnh đầy đủ, hãy cân nhắc những điều sau:
-
Thành phần hiển thị chung nhất mà bạn có thể mở rộng là
View, vì vậy, bạn thường bắt đầu bằng cách mở rộng thành phần hiển thị này để tạo thành phần siêu mới của mình. - Bạn có thể cung cấp một hàm khởi tạo có thể lấy các thuộc tính và tham số từ tệp XML, đồng thời bạn cũng có thể sử dụng các thuộc tính và tham số tương tự của riêng mình, chẳng hạn như màu và dải ô của đồng hồ VU hoặc chiều rộng và tốc độ chuyển động của kim đồng hồ.
- Bạn cũng có thể tạo các trình nghe sự kiện, trình truy cập thuộc tính và công cụ sửa đổi, cũng như có thể có hành vi phức tạp hơn trong lớp thành phần.
-
Bạn hầu như chắc chắn muốn ghi đè
onMeasure()và cũng có thể cần phải ghi đèonDraw()nếu bạn muốn thành phần hiển thị nội dung nào đó. Mặc dù cả hai đều có hành vi mặc định, nhưngonDraw()mặc định sẽ không thực hiện hành động nào vàonMeasure()mặc định sẽ luôn thiết lập kích thước 100x100 (có thể đây không phải là kích thước mà bạn muốn). -
Bạn cũng có thể ghi đè các phương thức
onkhác (nếu cần).
Mở rộng onDraw() và onMeasure()
Phương thức onDraw() cung cấp một Canvas mà bạn có thể triển khai bất kỳ nội dung nào mình muốn: đồ hoạ 2D, các thành phần chuẩn hoặc tuỳ chỉnh khác, văn bản được tạo kiểu hoặc bất kỳ nội dung nào khác mà bạn có thể nghĩ đến.
onMeasure() có liên quan nhiều hơn một chút. onMeasure() là một phần quan trọng trong mối tương quan về kết xuất giữa thành phần và vùng chứa của thành phần đó. onMeasure() phải được ghi đè để báo cáo hiệu quả và chính xác các kết quả đo lường của các bộ thành phần chứa trong đó. Điều này được thực hiện phức tạp hơn một chút bởi các yêu cầu về giới hạn từ phương thức mẹ (được chuyển vào phương thức onMeasure()) và do yêu cầu phải gọi phương thức setMeasuredDimension() với chiều rộng và chiều cao đo được sau khi đã được tính toán. Nếu bạn không gọi phương thức này từ một phương thức onMeasure() bị ghi đè, thì kết quả sẽ là một ngoại lệ tại thời điểm đo lường.
Nhìn chung, việc triển khai onMeasure() sẽ có dạng như sau:
-
Phương thức
onMeasure()bị ghi đè được gọi với thông số chiều rộng và chiều cao, được xem là yêu cầu đối với các quy định hạn chế về số đo chiều rộng và chiều cao mà bạn tạo. Tham sốwidthMeasureSpecvàheightMeasureSpecđều là mã số nguyên đại diện cho kích thước. Bạn có thể tham khảo toàn bộ thông tin về loại hạn chế mà các thông số kỹ thuật này có thể yêu cầu trong tài liệu tham khảo bên dướiView.onMeasure(int, int). Tài liệu tham khảo này cũng giải thích toàn bộ hoạt động đo lường. -
Phương thức
onMeasure()của thành phần sẽ tính toán chiều rộng và chiều cao đo lường cần thiết để kết xuất thành phần. Phương thức này phải cố gắng duy trì các thông số kỹ thuật được chuyển vào, mặc dù có thể vượt quá những thông số đó. Trong trường hợp này, lớp cha có thể chọn những việc cần làm, bao gồm cắt, cuộn, gửi ngoại lệ hoặc yêu cầuonMeasure()thử lại, có thể là với các thông số đo lường khác. -
Khi tính chiều rộng và chiều cao, hãy gọi phương thức
setMeasuredDimension(int width, int height)với kết quả đo lường đã tính toán. Nếu không thực hiện việc này, một ngoại lệ sẽ xảy ra.
Dưới đây là phần tóm tắt về một số phương thức tiêu chuẩn khác mà khung này gọi trên các thành phần hiển thị:
| Danh mục | Phương pháp | Mô tả |
|---|---|---|
| Tạo | Hàm khởi tạo | Có một dạng hàm khởi tạo được gọi khi thành phần hiển thị được tạo từ mã và một dạng được gọi khi thành phần hiển thị được bung nén từ tệp bố cục. Dạng hàm khởi tạo thứ hai phân tích cú pháp và áp dụng các thuộc tính được xác định trong tệp bố cục. |
|
Được gọi sau khi một khung hiển thị và tất cả các khung hiển thị con đều được tăng cường từ XML. | |
| Bố cục | |
Được gọi để xác định các yêu cầu về kích thước cho thành phần hiển thị này và tất cả các thành phần con. |
|
Được gọi khi thành phần hiển thị này phải gán một kích thước và vị trí cho tất cả các thành phần con. | |
|
Được gọi khi kích thước của thành phần hiển thị này thay đổi. | |
| Đang vẽ | |
Được gọi khi khung hiển thị phải kết xuất nội dung. |
| Xử lý sự kiện | |
Được gọi khi một sự kiện nhấn phím xảy ra. |
|
Được gọi khi một sự kiện nhả phím xảy ra. | |
|
Được gọi khi một sự kiện chuyển động bi xoay xảy ra. | |
|
Được gọi khi một sự kiện chuyển động trên màn hình cảm ứng xảy ra. | |
| Tập trung | |
Được gọi khi thành phần hiển thị nhận hoặc mất tâm điểm. |
|
Được gọi khi cửa sổ chứa thành phần hiển thị nhận hoặc mất tâm điểm. | |
| Đính kèm | |
Được gọi khi thành phần hiển thị được đính kèm vào cửa sổ. |
|
Được gọi khi thành phần hiển thị được tách khỏi cửa sổ. | |
|
Được gọi khi chế độ hiển thị của cửa sổ chứa thành phần hiển thị thay đổi. |
Thành phần điều khiển phức hợp
Nếu bạn không muốn tạo một thành phần được tuỳ chỉnh hoàn toàn mà thay vào đó bạn muốn kết hợp một thành phần có thể sử dụng lại bao gồm một nhóm các thành phần điều khiển hiện có, thì việc tạo một thành phần phức hợp (hoặc thành phần điều khiển phức hợp) có thể là lựa chọn phù hợp nhất. Tóm lại, việc này sẽ kết hợp một số thành phần điều khiển hoặc thành phần hiển thị không thể chia nhỏ vào một nhóm các mục về mặt logic có thể xem là một đối tượng đơn lẻ.
Ví dụ: hộp kết hợp có thể là sự kết hợp của một trường EditText dòng đơn và một nút liền kề có danh sách bật lên được đính kèm. Nếu người dùng nhấn vào nút và chọn một nội dung nào đó trong danh sách, thao tác này sẽ điền vào trường EditText, nhưng họ cũng có thể trực tiếp nhập nội dung nào đó vào EditText nếu muốn.
Trên Android, có hai khung hiển thị khác đã sẵn sàng để thực hiện việc này: Spinner và AutoCompleteTextView. Bất kể điều gì, khái niệm này về hộp kết hợp là một ví dụ điển hình.
Để tạo thành phần phức hợp, hãy làm như sau:
-
Giống như với một
Activity, hãy sử dụng phương pháp khai báo (dựa trên XML) để tạo các thành phần được chứa hoặc lập trình để lồng ghép các thành phần này từ mã. Chúng ta thường bắt đầu với mộtLayoutthuộc loại nào đó, vì vậy, hãy tạo một lớp mở rộng mộtLayout. Trong trường hợp hộp kết hợp, bạn có thể sử dụngLinearLayoutvới hướng ngang. Bạn có thể lồng các bố cục khác vào bên trong, nên thành phần phức hợp có thể có bất kỳ độ phức tạp và cấu trúc nào. -
Trong hàm khởi tạo của lớp mới, hãy lấy bất kỳ tham số nào mà lớp cha dự kiến và chuyển các tham số đó đến hàm khởi tạo của lớp cha trước. Sau đó, bạn có thể thiết lập các thành phần hiển thị khác để sử dụng trong thành phần mới. Đây là nơi bạn tạo trường
EditTextvà danh sách bật lên. Bạn có thể đưa các thuộc tính và tham số của riêng mình vào XML mà hàm khởi tạo có thể lấy ra và sử dụng. -
Bạn có thể tạo trình nghe cho những sự kiện mà các thành phần hiển thị được chứa có thể tạo. Ví dụ: một phương thức trình nghe dành cho trình nghe lượt nhấp vào mục trong danh sách để cập nhật nội dung của
EditTextnếu một lựa chọn trong danh sách đã được thực hiện. -
Bạn có thể tuỳ ý tạo các thuộc tính riêng bằng trình truy cập và công cụ sửa đổi. Ví dụ: cho phép thiết lập giá trị
EditTextban đầu trong thành phần và truy vấn nội dung của thành phần đó khi cần. -
Bạn có thể ghi đè
onDraw()vàonMeasure()(không bắt buộc). Việc này thường không cần thiết khi mở rộng mộtLayout, vì bố cục có hành vi mặc định có thể hoạt động tốt. -
Bạn có thể ghi đè các phương thức
onkhác, chẳng hạn nhưonKeyDown(), ví dụ: để chọn một số giá trị mặc định nhất định từ danh sách bật lên của hộp kết hợp khi nhấn một phím nhất định.
Việc sử dụng Layout làm cơ sở cho một thành phần điều khiển tuỳ chỉnh mang lại nhiều lợi ích, bao gồm cả những lợi ích sau:
- Bạn có thể chỉ định bố cục bằng cách sử dụng tệp XML khai báo, giống như với màn hình hoạt động, hoặc bạn có thể tạo các thành phần hiển thị theo phương pháp lập trình và lồng các thành phần này vào bố cục từ mã.
-
Các phương thức
onDraw()vàonMeasure()(cùng với hầu hết các phương thứconkhác) có hành vi phù hợp nên bạn không phải ghi đè các phương thức đó. - Bạn có thể nhanh chóng tạo các thành phần hiển thị phức tạp tuỳ ý và sử dụng lại như thể chúng là một thành phần duy nhất.
Sửa đổi kiểu thành phần hiển thị hiện tại
Nếu có một thành phần tương tự như nội dung bạn muốn, bạn có thể mở rộng thành phần đó và ghi đè hành vi mà bạn muốn thay đổi. Bạn có thể làm tất cả những việc cần làm với một thành phần được tuỳ chỉnh đầy đủ. Tuy nhiên, nếu bắt đầu bằng việc sử dụng một lớp chuyên biệt hơn trong hệ phân cấp View, thì trong đó cũng có thể có sẵn một số hành vi phù hợp với yêu cầu của bạn.
Ví dụ: ứng dụng mẫu NotePad minh hoạ nhiều khía cạnh của việc sử dụng nền tảng Android. Một trong những khía cạnh đó chính là việc mở rộng thành phần hiển thị EditText để tạo bảng ghi chú có chia dòng. Đây không phải là một ví dụ hoàn hảo, cũng như các API để thực hiện điều này có thể thay đổi, nhưng ví dụ này đã thể hiện được các nguyên tắc.
Nếu bạn chưa từng thực hiện, hãy nhập mẫu NotePad vào Android Studio hoặc xem mã nguồn bằng đường liên kết được cung cấp. Cụ thể, hãy xem định nghĩa của LinedEditText trong tệp NoteEditor.java.
Dưới đây là một số điểm cần lưu ý trong tệp này:
-
Định nghĩa
Lớp này được khai báo bằng dòng sau:
public static class LinedEditText extends EditTextLinedEditTextđược xác định là một lớp bên trong trong hoạt độngNoteEditor, nhưng lớp này là công khai để có thể được truy cập dưới dạngNoteEditor.LinedEditTexttừ bên ngoài lớpNoteEditor.Ngoài ra,
LinedEditTextlàstatic, nghĩa là lớp này không tạo ra cái gọi là "phương thức tổng hợp" để cho phép truy cập dữ liệu từ lớp mẹ. Điều này có nghĩa là lớp này hoạt động như một lớp riêng biệt thay vì liên quan chặt chẽ đếnNoteEditor. Đây là một cách rõ ràng hơn để tạo các lớp trong nếu các lớp đó không cần quyền truy cập vào trạng thái của lớp ngoài. Cách này giúp lớp được tạo có kích thước nhỏ và cho phép dễ dàng sử dụng từ các lớp khác.LinedEditTextmở rộngEditText, đây là khung hiển thị cần tuỳ chỉnh trong trường hợp này. Khi hoàn tất, lớp mới có thể thay thế cho một thành phần hiển thịEditTextthông thường. -
Khởi chạy lớp
Như thường lệ, lớp cha sẽ được gọi trước tiên. Đây không phải là một hàm khởi tạo mặc định, mà là một hàm có tham số.
EditTextđược tạo bằng các tham số này khi được bung nén từ một tệp bố cục XML. Do đó, hàm khởi tạo cũng cần nhận các tham số này và chuyển chúng đến hàm khởi tạo của lớp cha. -
Phương thức bị ghi đè
Ví dụ này chỉ ghi đè phương thức
onDraw(), nhưng bạn có thể cần phải ghi đè các phương thức khác khi tạo các thành phần tuỳ chỉnh của riêng mình.Đối với mẫu này, việc ghi đè phương thức
onDraw()cho phép bạn vẽ các đường màu xanh dương trên canvas thành phần hiển thịEditText. Canvas được truyền vào phương thứconDraw()bị ghi đè. Phương thứcsuper.onDraw()được gọi trước khi phương thức kết thúc. Bạn phải gọi phương thức của lớp cấp cao. Trong trường hợp này, hãy gọi phương thức này ở bước cuối cùng, sau khi vẽ xong các dòng mà bạn muốn đưa vào. -
Thành phần tuỳ chỉnh
Giờ đây, bạn đã có thành phần tuỳ chỉnh, nhưng làm cách nào để sử dụng thành phần này? Trong ví dụ về NotePad, thành phần tuỳ chỉnh được dùng trực tiếp từ bố cục khai báo, vì vậy, hãy xem
note_editor.xmltrong thư mụcres/layout:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
Thành phần tuỳ chỉnh được tạo dưới dạng thành phần hiển thị chung trong XML và lớp được chỉ định bằng cách sử dụng gói đầy đủ. Lớp trong mà bạn xác định được tham chiếu bằng ký hiệu
NoteEditor$LinedEditText, đây là cách tiêu chuẩn để tham chiếu đến các lớp trong trong ngôn ngữ lập trình Java.Nếu thành phần thành phần hiển thị tuỳ chỉnh không được khai báo là một lớp trong thì bạn có thể khai báo thành phần đó bằng tên phần tử XML và loại trừ thuộc tính
class. Ví dụ:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
Xin lưu ý rằng lớp
LinedEditTexthiện là một tệp lớp riêng biệt. Khi lớp đó được lồng trong lớpNoteEditor, kỹ thuật này sẽ không hoạt động.Các thuộc tính và tham số khác trong phần khai báo là những thuộc tính và tham số được truyền vào hàm khởi tạo thành phần tuỳ chỉnh, sau đó được truyền qua hàm khởi tạo
EditText. Do đó, đây cũng là các tham số mà bạn sử dụng cho một thành phần hiển thịEditText. Bạn cũng có thể thêm các thông số của riêng mình.
Việc tạo các thành phần tuỳ chỉnh chỉ phức tạp khi bạn cần.
Một thành phần tinh vi hơn có thể ghi đè nhiều phương thức on hơn nữa, đồng thời ra mắt các phương thức trợ giúp riêng, giúp tuỳ chỉnh các thuộc tính và hành vi của thành phần đó một cách hiệu quả. Hạn chế duy nhất là trí tưởng tượng của bạn và việc bạn cần thành phần này làm.