Các kỹ thuật xử lý sự kiện trên view

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: ViewViewGroup. Để bắt đầu, nền tảng bao gồm nhiều loại lớp con Thành phần hiển thị (View) và Nhóm thành phần hiển thị (ViewGroup) được tạo sẵn — lần lượt được gọi là các tiện ích widget và các bố cục — mà bạn có thể sử 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,

<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
0
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
1, cũng như
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
2 với mục đích đặc biệt hơn,
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
3, và
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
4.

Trong số các bố cục có sẵn gồm

<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
5,
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
6,
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
7, và các bố cục khác. Để xem thêm ví dụ, hãy xem phần Các đối tượng 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 Thành phần hiển thị (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 widget hoặc bố cục hiện có, bạn chỉ cần 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 Thành phần hiển thị (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 Thành phần hiển thị kết xuất hoàn toàn tuỳ chỉnh; chẳng hạn, một núm "điều khiển âm lượng" được kết xuất bằng đồ hoạ 2D và 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 hiển thị vào một thành phần mới duy nhất, có thể là để tạo thành một thành phần nào đó như một ComboBox (kết hợp danh sách bật lên và trường văn bản nhập tự do), một thành phần điều khiển trình chọn ngăn kép (một ngăn có hai bên trái-phải với một danh sách mỗi bên, trong đó bạn có thể chỉ định lại mục nào trong danh sách nào), v.v.
  • Bạn có thể ghi đè phương thức kết xuất một thành phần Chỉnh sửa văn bản (EditText) trên màn hình (Notepad Tutorial (Hướng dẫn về 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 như các 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 dưới đâ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

Dưới đây là thông tin tổng quan cấp cao về những điều bạn cần biết để bắt đầu tạo các Thành phần hiển thị của riêng bạn:

  1. Mở rộng một lớp View hoặc lớp con hiện có bằng lớp của riêng bạn.
  2. 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 "View0", ví dụ: View1, View2 và View3. Điều này tương tự như các sự kiện View4 trong View5 hoặc View6 mà bạn ghi đè cho các vòng đời và các chức năng hook khác.
  3. 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 của mình thay cho thành phần hiển thị mà lớp đó dựa trên.

Mẹo: Bạn có thể khai báo các lớp mở rộng là các lớp trong trong các hoạt động sử dụng các lớp này. Định nghĩa này hữu ích vì sẽ kiểm soát quyền truy cập vào các lớp đó nhưng không phải lúc nào cũng cần thiết (có thể bạn muốn tạo Thành phần hiển thị công khai mới để sử dụng rộng rãi hơn trong ứng dụng).

Thành phần được tuỳ chỉnh đầy đủ

Bạn có thể dùng các thành phần được tuỳ chỉnh đầy đủ để tạo các thành phần đồ hoạ xuất hiện theo ý muốn. Có thể là 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ừ để bạn có thể hát theo. Dù thế nào đi nữa, thành phần mà bạn muố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.

May mắn là bạn có thể dễ dàng tạo các thành phần và giao diện theo bất kỳ cách nào mà bạn thích, chỉ giới hạn bởi trí tưởng tượng, kích thước màn hình và khả năng xử lý hiện có (nên nhớ rằng cuối cùng thì ứng dụng của bạn có thể sẽ phải chạy trên một thiết bị nào đó không mạnh mẽ như máy trạm của bạn).

Để tạo một thành phần tuỳ chỉnh đầy đủ:

  1. Không có gì phải ngạc nhiên khi 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.
  2. 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ồ, v.v.)
  3. 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.
  4. Bạn hầu như chắc chắn muốn ghi đè View8 và cũng có thể cần phải ghi đè View9 nếu bạn muốn thành phần hiển thị trình bày nội dung nào đó. Mặc dù cả hai đều có hành vi mặc định, nhưng View9 mặc định sẽ không thực hiện hành động nào và View8 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).
  5. Các phương thức View4 khác cũng có thể bị ghi đè theo yêu cầu.

Mở rộng View9 và View8

Phương thức View9 cung cấp cho bạn một ViewGroup6 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.

Lưu ý: Phương thức này không áp dụng cho đồ hoạ 3D. Nếu muốn sử dụng đồ hoạ 3D, bạn phải mở rộng ViewGroup7 thay vì Thành phần hiển thị, và vẽ từ một luồng riêng. Hãy xem mẫu GLSurfaceViewActivity để biết thêm chi tiết.

View8 có liên quan nhiều hơn một chút. View8 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 đó. View8 nên đượ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 cha (được chuyển vào phương thức View8) và do yêu cầu phải gọi phương thức Button2 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 được phương thức này từ một phương thức View8 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 View8 sẽ có dạng như sau:

  1. Phương thức View8 bị ghi đè được gọi với thông số đo chiều rộng và chiều cao (tham số Button6 và Button7, cả hai đều là mã số nguyên đại diện cho thứ nguyên) cần đượ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 nên tạo. 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ưới Button8 (tài liệu tham khảo này giải thích khá rõ về toàn bộ hoạt động đo lường).
  2. Phương thức View8 của thành phần phải 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 sẽ cố gắng duy trì các thông số kỹ thuật được chuyển vào, mặc dù có thể chọn 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ầu View8 thử lại, có thể là với các thông số đo lường khác).
  3. Sau khi tính chiều rộng và chiều cao, bạn phải gọi phương thức TextView1 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ẽ được gửi.

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ụcPhương phápMô tảTạoHàm khởi tạoCó 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 nên phân tích cú pháp và áp dụng mọi thuộc tính được xác định trong tệp bố cục.TextView2Được gọi sau khi một thành phần hiển thị và tất cả các thành phần con đều được bung nén từ XML.Bố cụcTextView3Đượ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.TextView4Được gọi khi thành phần hiển thị này gán một kích thước và vị trí cho tất cả các thành phần con.TextView5Được gọi khi kích thước của thành phần hiển thị này thay đổi.VẽTextView6Được gọi khi thành phần hiển thị kết xuất nội dung.Xử lý sự kiệnTextView7Được gọi khi một sự kiện nhấn phím mới xảy ra.TextView8Được gọi khi một sự kiện nhả phím xảy ra.TextView9Được gọi khi một sự kiện chuyển động bi xoay xảy ra.EditText0Đượ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âm điểmEditText1Được gọi khi thành phần hiển thị nhận hoặc mất tâm điểm.EditText2Đượ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èmEditText3Được gọi khi thành phần hiển thị được đính kèm vào cửa sổ.EditText4Được gọi khi thành phần hiển thị được tách khỏi cửa sổ.EditText5Đượ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 (Compound Control)

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ó, bạn có thể tạo Thành phần phức hợp (hoặc Thành phần điều khiển phức hợp - Compound Control) phù hợp với nhu cầu sử dụng. Tóm lại, việc này sẽ kết hợp một số thành phần điều khiển không thể chia nhỏ (hoặc thành phần hiển thị) 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ẻ. Chẳng hạn, bạn có thể xem Hộp kết hợp (Combo Box) là một tổ hợp của một trường Chỉnh sửa văn bản (EditText) dòng đơn và một nút nhấn liền kề với Danh sách bật lên (PopupList) đính kèm. Khi nhấn nút và chọn một nội dung nào đó từ danh sách, thao tác này sẽ điền vào trường Chỉnh sửa văn bản (EditText); nhưng người dùng cũng có thể trực tiếp nhập nội dung nào đó vào trường Chỉnh sửa văn bản (EditText) nếu muốn.

Trên Android, thật ra có hai Thành phần hiển thị (View) khác đã sẵn sàng để thực hiện việc này:

<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
1 và
<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />
2; nhưng bất kể như thế nào thì khái niệm về Combo Box (Hộp kết hợp) sẽ giúp bạn dễ dàng hiểu được ví dụ.

Để tạo thành phần phức hợp, hãy làm như sau:

  1. Chúng ta thường bắt đầu với một Bố cục thuộc loại nào đó, vì vậy, hãy tạo một lớp mở rộng một Bố cục. Có thể, trong trường hợp một Hộp kết hợp, chúng ta có thể sử dụng một Bố cục tuyến tính (LinearLayout) với hướng ngang. Hãy nhớ rằng các bố cục khác có thể được lồng 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. Xin lưu ý rằng giống như với một Hoạt động (Activity), bạn có thể 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 (contained component), hoặc bạn có thể lập trình để lồng ghép các thành phần này từ mã.
  2. 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, rồi 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 cũng là nơi bạn sẽ tạo trường Chỉnh sửa văn bản (EditText) và Danh sách bật lên (PopupList). Lưu ý rằng bạn cũng 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.
  3. Bạn cũng có thể tạo các 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, chẳng hạn như một phương thức trình nghe dành cho Trình nghe lượt nhấp trong Danh sách các mục (List Item Click Listener) để cập nhật nội dung của Chỉnh sửa văn bản (EditText) nếu một lựa chọn trong danh sách đã được thực hiện.
  4. Bạn cũng có thể tạo các thuộc tính riêng bằng trình truy cập và công cụ sửa đổi, chẳng hạn như cho phép thiết lập giá trị Chỉnh sửa văn bản (EditText) ban đầu trong thành phần và truy vấn nội dung của thành phần đó khi cần.
  5. Trong trường hợp mở rộng một Bố cục, bạn không cần ghi đè phương thức View9 và View8 vì bố cục đó sẽ có hành vi mặc định có thể hoạt động tốt. Tuy nhiên, bạn vẫn có thể ghi đè nếu cần.
  6. Bạn có thể ghi đè các phương thức View4 khác, chẳng hạn như ListView1, có thể là để 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.

Tóm lại, việc sử dụng một Bố cục làm cơ sở cho một Thành phần điều khiển tuỳ chỉnh có một số lợi thế, bao gồm:

  • 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 View9 và View8 (cùng với hầu hết các phương thức View4 khác) có thể sẽ có hành vi phù hợp nên bạn không phải ghi đè các phương thức đó.
  • Cuối cùng, bạn có thể thực hiện rất nhanh việc xây dựng các thành phần hiển thị với độ phức tạp tuỳ ý và sử dụng lại như thể 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

Thậm chí còn có một tuỳ chọn dễ dàng hơn để tạo một Thành phần hiển thị tuỳ chỉnh, thành phần này sẽ hữu ích trong một số trường hợp nhất định. Nếu có một thành phần đã rất giống với nội dung mà bạn muốn, bạn chỉ cần mở rộng thành phần đó và chỉ 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 thành phần hiển thị thì trong đó cũng có thể có sẵn nhiều hành vi phù hợp với yêu cầu của bạn.

Ví dụ: Ứng dụng NotePad thể hiện 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ị Chỉnh sửa văn bản (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 chỉ xem mã nguồn bằng đường liên kết được cung cấp). Cụ thể, hãy lưu ý đến phần khai báo của ListView5 trong tệp NoteEditor.java.

Dưới đây là một số điểm cần lưu ý trong tệp này:

  1. Phần khai báo

    Lớp này được khai báo bằng dòng sau:
    ListView6

    • ListView5 được khai báo là một lớp bên trong trong hoạt động ListView8, nhưng lớp này là công khai để có thể được truy cập dưới dạng ListView9 từ bên ngoài lớp ListView8 nếu muốn.
    • Lớp này là CheckBox1, nghĩa là không tạo ra những "phương thức tổng hợp" để cho phép truy cập dữ liệu từ lớp cha; đồng nghĩa với việc hoạt động như một lớp riêng biệt thay vì liên quan chặt chẽ đến ListView8. Đâ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, giữ cho 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.
    • Phần khai báo này mở rộng CheckBox3, chính là Thành phần hiển thị được chọn để tuỳ chỉnh trong trường hợp này. Khi hoàn tất, lớp mới sẽ có thể thay thế cho một thành phần hiển thị CheckBox3 thông thường.
  2. Khởi chạy lớp

    Như thường lệ, lớp cha sẽ được gọi trước tiên. Hơn nữa, đâ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ố. Thành phần Chỉnh sửa văn bản (Edit Text) được tạo với các tham số này khi được bung nén từ tệp bố cục XML, do đó hàm khởi tạo cần thực hiện cả việc nhận các tham số này và chuyển các tham số đó đến hàm khởi tạo của lớp cha.

  3. Phương thức bị ghi đè

    Ví dụ này chỉ ghi đè một phương thức là View9, 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 View9 cho phép vẽ các đường màu xanh dương trên canvas thành phần hiển thị CheckBox3 (canvas được chuyển vào phương thức bị ghi đè View9). Phương thức super.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 cha phải được gọi, và trong trường hợp này, chúng ta sẽ gọi phương thức này ở bước cuối cùng, sau khi vẽ xong các dòng mà chúng ta muốn đưa vào.

  4. Sử dụng Thành phần tuỳ chỉnh

    Chúng tôi hiệ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 CheckBox9 trong thư mục res/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 đủ. Xin lưu ý rằng lớp trong mà chúng ta đã khai báo được tham chiếu bằng ký hiệu RadioButton0, đâ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 RadioButton1. Ví dụ:

      <com.example.android.notepad.LinedEditText
        id="@+id/note"
        ... />
      

      Xin lưu ý rằng lớp ListView5 hiện là một tệp lớp riêng biệt. Khi lớp đó được lồng trong lớp ListView8, 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 được chuyển vào hàm khởi tạo thành phần tuỳ chỉnh, sau đó được chuyển qua hàm khởi tạo Chỉnh sửa văn bản (EditText). Do đó, đây cũng là các tham số mà bạn sẽ sử dụng cho thành phần hiển thị Chỉnh sửa văn bản (EditText). Xin lưu ý rằng bạn cũng có thể thêm các tham số của riêng mình và chúng ta sẽ đề cập lại vấn đề này dưới đây.

Đó là tất cả những gì bạn cần làm. Cũng phải thừa nhận rằng, đây là trường hợp đơn giản, nhưng từ đó cũng nảy sinh vấn đề — 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 View4 hơn nữa, đồng thời ra mắt một số 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.