Android cung cấp một mô hình được sắp xếp 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,
LinearLayout
,
FrameLayout
,
RelativeLayout
,
và các bố cục khác. Để biết thêm ví dụ, hãy xem phần Bố cục phổ biến.
Nếu không có tiện ích hoặc bố cục tạo sẵn nào đáp ứng được nhu cầu của mình, 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 những đ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 của tiện ích hoặc bố cục đó.
Việc tạo các lớp con View
riêng giúp bạn kiểm soát chính xác giao diện và chức năng của một phần tử màn hình. Để giúp bạn nắm được thành phần điều khiển bạn có thể dùng với thành phần hiển thị tuỳ chỉnh, sau đây là một số ví dụ về những việc bạn có thể làm với các thành phần đó:
-
Bạn có thể tạo một loại
View
được kết xuất tuỳ chỉnh hoàn toàn – ví dụ: núm "điều khiển âm lượng" được kết xuất bằng đồ hoạ 2D, giống như bộ điều khiển điện tử analog. -
Bạn có thể kết hợp một nhóm các thành phần
View
vào một thành phần mới, có thể là để tạo một kết hợp (kết hợp danh sách bật lên và trường văn bản tự do), điều khiển bộ chọn hai ngăn (ngăn bên trái và bên phải chứa danh sách để 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 kết xuất thành phần
EditText
trên màn hình. Ứng dụng mẫu NotePad sử dụng tính năng này để tạo ra một trang sổ tay có dòng kẻ. - Bạn có thể ghi lại các sự kiện khác (như các thao tác nhấn phím) và xử lý các sự kiện đó theo cách tuỳ chỉnh, chẳng hạn như đối với trò chơi.
Các phần sau đây giải thích cách tạo khung hiển thị tuỳ chỉnh và cách sử dụng các khung hiển thị đó 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 cơ bản
Dưới đây là thông tin tổng quan cấp cao về những điều bạn cần biết để tạo thành phần View
của riêng mình:
-
Mở rộng một lớp hoặc lớp con
View
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 cấp cao để 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ệnon
trongActivity
hoặcListActivity
mà 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 tiện ích mới thay cho khung hiển thị dựa trên lớp mở rộng đó.
Thành phần được tuỳ chỉnh toàn bộ
Bạn có thể tạo các thành phần đồ hoạ được tuỳ chỉnh hoàn toàn xuất hiện theo cách bạn muốn. Có thể bạn cần một đồng hồ VU dạng đồ hoạ trông giống như một đồng hồ đo kim loại cũ, hoặc một khung hiển thị văn bản hát theo, trong đó một quả bóng nảy lên di chuyển theo lời khi bạn hát theo máy hát karaoke. Bạn có thể muốn một thứ mà các thành phần tích hợp sẵn không làm được, bất kể bạn kết hợp chúng theo cách nào.
May mắn là bạn có thể tạo các thành phần có giao diện và hoạt động theo bất kỳ cách nào bạn 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ý có sẵn, lưu ý rằng ứng dụng của bạn có thể phải chạy trên một thiết bị nào đó có công suất thấp hơn đáng kể so với máy trạm của máy tính.
Để tạo một thành phần được tuỳ chỉnh hoàn toàn, hãy cân nhắc những việc sau:
-
Khung 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 khung hiển thị này để tạo siêu thành phần mới. - 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ừ XML, đồng thời bạn có thể sử dụng các thuộc tính và tham số đó của riêng mình, chẳng hạn như màu sắc và phạm vi của đồng hồ VU hoặc chiều rộng và giảm chấn của kim.
- Bạn nên tạo trình nghe sự kiện, trình truy cập thuộc tính và đối tượng sửa đổi của riêng mình cũng như hành vi tinh vi hơn trong lớp thành phần.
-
Bạn gần 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 không làm gì cả vàonMeasure()
mặc định luôn đặt kích thước là 100x100 mà có thể bạn không muốn. -
Nếu cần, bạn cũng có thể ghi đè các phương thức
on
khác.
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 mọi nội dung mình muốn: đồ hoạ 2D, các thành phần tiêu chuẩn hoặc tuỳ chỉnh khác, văn bản được tạo kiểu hoặc bất cứ thành phần 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 hợp đồng hiển thị giữa thành phần và vùng chứa của thành phần đó. Bạn phải ghi đè onMeasure()
để báo cáo hiệu quả và chính xác số liệu đo lường các phần chứa trong đó. Việc này phức tạp hơn một chút bởi các yêu cầu giới hạn từ phần tử mẹ (được truyền vào phương thức onMeasure()
) và theo yêu cầu gọi phương thức setMeasuredDimension()
có chiều rộng và chiều cao đo được sau khi 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ì
sẽ dẫn đến một ngoại lệ tại thời điểm đo lường.
Ở cấp độ cao, 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ác thông số này được coi là yêu cầu đối với các hạn chế đối với số đo chiều rộng và chiều cao mà bạn tạo ra. Tham sốwidthMeasureSpec
vàheightMeasureSpec
đều là mã số nguyên đại diện cho các phương diện. Bạn có thể xem tài liệu tham khảo đầy đủ 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 tại mụcView.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 để hiển thị thành phần. Thuộc tính này phải cố gắng tuân thủ các thông số kỹ thuật được truyền vào, mặc dù có thể vượt quá các thông số kỹ thuật đó. Trong trường hợp này, phần tử mẹ có thể chọn việc cần làm, bao gồm cắt đoạn, cuộn, loại bỏ một trường hợp 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 chiều rộng và chiều cao được tính toán, hãy gọi phương thức
setMeasuredDimension(int width, int height)
có số đo đã tính. Không thực hiện được điều này sẽ dẫn đến một ngoại lệ.
Dưới đây là bản tóm tắt các phương thức chuẩn khác mà khung này gọi trên khung hiển thị:
Danh mục | Phương pháp | Nội dung 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 tăng cường từ tệp bố cục. Dạng 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 khung hiển thị này phải gán một kích thước và vị trí cho tất cả các khung hiển thị con. | |
|
Được gọi khi kích thước của thành phần hiển thị này thay đổi. | |
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. | |
Trọng tâm |
|
Đượ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 khung hiển thị thay đổi. |
Chế độ đ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à tìm cách kết hợp một thành phần có thể tái sử dụng gồm một nhóm thành phần điều khiển hiện có, thì tốt nhất bạn nên tạo thành phần kết hợp (hoặc thành phần điều khiển kết hợp). Tóm lại, tính năng này tập hợp nhiều chế độ xem hoặc chế độ điều khiển nguyên tử hơn thành một nhóm mục hợp lý có thể được coi là một đối tượng duy nhất.
Ví dụ: hộp kết hợp có thể là sự kết hợp giữa trường EditText
dòng đơn và nút liền kề có danh sách cửa sổ bật lên đính kèm. Nếu người dùng nhấn vào nút và chọn nội dung nào đó trong danh sách, thao tác đó sẽ điền vào trường EditText
. Tuy nhiên, họ cũng có thể nhập trực tiếp nội dung nào đó vào EditText
nếu muốn.
Trong Android, bạn có thể sử dụng 2 khung hiển thị khác để thực hiện việc này: Spinner
và AutoCompleteTextView
. Dù vậy, khái niệm về hộp kết hợp này 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:
-
Tương tự như với
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 chứa hoặc lồng các thành phần đó theo phương thức lập trình từ mã của bạn. Điểm xuất phát thông thường là mộtLayout
thuộc loại nào đó, vì vậy, hãy tạo một lớp mở rộngLayout
. Trong trường hợp hộp kết hợp, bạn có thể sử dụngLinearLayout
có hướng ngang. Bạn có thể lồng các bố cục khác vào bên trong, vì vậy, thành phần phức hợp có thể có cấu trúc và phức tạp tuỳ ý. -
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 cấp cao dự kiến rồi chuyển các tham số đó đến hàm khởi tạo lớp cấp cao trước tiên. 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
EditText
và 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ể kéo và sử dụng. -
Nếu muốn, hãy tạo trình nghe cho những sự kiện mà các khung hiển thị được chứa của bạn có thể tạo ra. Ví dụ: phương thức trình nghe 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
EditText
nếu lựa chọn danh sách. -
Bạn có thể tạo các thuộc tính của riêng mình bằng trình truy cập và đối tượng sửa đổi (không bắt buộc). Ví dụ: hãy đặt giá trị
EditText
ban đầu trong thành phần và truy vấn nội dung của thành phần đó khi cần. -
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ộngLayout
, vì bố cục này có hành vi mặc định có thể hoạt động tốt. -
(Không bắt buộc) Ghi đè các phương thức
on
khác, chẳng hạn nhưonKeyDown()
, chẳng hạn như để 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 người dùng nhấn vào một phím nhất định.
Việc sử dụng Layout
làm cơ sở cho chế độ điều khiển tuỳ chỉnh có nhiều lợi ích, bao gồm:
- Bạn có thể chỉ định bố cục bằng cách sử dụng các 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 thức lập trình và lồng các thành phần này vào bố cục từ mã của bạn.
-
Phương thức
onDraw()
vàonMeasure()
, cùng với hầu hết các phương thứcon
khác đều có hành vi phù hợp, vì vậy, bạn không phải ghi đè các phương thức này. - Bạn có thể nhanh chóng tạo các thành phần hiển thị phức hợ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 loại chế độ xem hiện tại
Nếu có một thành phần tương tự như thành phần 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 mọi việc với một thành phần được tuỳ chỉnh hoàn toàn, nhưng bằng cách bắt đầu với một lớp chuyên biệt hơn trong hệ phân cấp View
, bạn có thể nhận được một số hành vi thực hiện miễn phí những gì bạn muố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. Trong đó có việc mở rộng khung hiển thị EditText
để tạo sổ tay có dòng chữ. Đây không phải là một ví dụ hoàn hảo và các API để thực hiện việc này có thể thay đổi, nhưng ví dụ này minh hoạ các nguyên tắc.
Nếu bạn chưa thực hiện việc này, hãy nhập mẫu NotePad vào Android Studio hoặc xem nguồn bằng đường liên kết được cung cấp. Cụ thể, hãy xem định nghĩa về 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 xác định bằng dòng sau:
public static class LinedEditText extends EditText
LinedEditText
được định nghĩa là một lớp bên trong trong hoạt độngNoteEditor
, nhưng lớp này ở chế độ công khai để có thể được truy cập dưới dạngNoteEditor.LinedEditText
từ bên ngoài lớpNoteEditor
.Ngoài ra,
LinedEditText
làstatic
, nghĩa là không tạo phương thức được gọi là "phương thức tổng hợp" cho phép truy cập vào dữ liệu của 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ì một lớp liên quan chặt chẽ đếnNoteEditor
. Đây là cách gọn gàng hơn để tạo các lớp bên trong nếu các lớp đó không cần quyền truy cập vào trạng thái từ lớp bên ngoài. Tệp 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 trong các lớp khác.LinedEditText
mở rộngEditText
, đây là thành phần hiển thị để tuỳ chỉnh trong trường hợp này. Khi bạn hoàn tất, lớp mới có thể thay thế cho một khung hiển thịEditText
thông thường. -
Khởi tạo 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 tăng cường từ tệp bố cục XML. Do đó, hàm khởi tạo cần nhận và truyền các lớp này đến hàm khởi tạo lớp cấp cao. -
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 chuyển vào phương thứconDraw()
bị ghi đè. Phương thứcsuper.onDraw()
được gọi trước khi phương thức này kết thúc. Phương thức của lớp cấp cao phải được gọi. Trong trường hợp này, hãy gọi phương thức đó ở cuối sau khi bạn vẽ các dòng mà bạn muốn đưa vào. -
Thành phần tuỳ chỉnh
Bây giờ, bạn đã có thành phần tuỳ chỉnh, nhưng làm cách nào để sử dụng? Trong ví dụ về NotePad, thành phần tuỳ chỉnh được sử dụng trực tiếp từ bố cục khai báo, vì vậy, hãy xem
note_editor.xml
trong 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 khung hiển thị chung trong XML và lớp được chỉ định bằng cách sử dụng gói đầy đủ. Lớp bên trong mà bạn định nghĩa đượ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 bên trong trong ngôn ngữ lập trình Java.Nếu thành phần khung hiển thị tuỳ chỉnh không được xác định là một lớp bên trong, bạn có thể khai báo thành phần khung hiển thị đó 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
LinedEditText
hiện là một tệp lớp riêng biệt. Khi lớp này được lồng vào 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 định nghĩa này là các 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 đến hàm khởi tạo
EditText
, vì vậy, đây cũng là các tham số mà bạn sử dụng cho khung hiển thịEditText
. Bạn cũng có thể thêm các tham số của riêng mình.
Việc tạo thành phần tuỳ chỉnh chỉ phức tạp theo mức 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 giới thiệu các phương thức trợ giúp riêng, giúp tuỳ chỉnh đáng kể các thuộc tính và hành vi của thành phần đó. 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 gì.