Hoạt động và ý định

1. Giới thiệu

Cho đến nay, bạn đã xử lý các ứng dụng chỉ có một hoạt động. Thực tế thì nhiều ứng dụng Android đòi hỏi nhiều hoạt động, cùng với việc điều hướng giữa các hoạt động đó.

Trong lớp học lập trình này, bạn sẽ xây dựng một ứng dụng từ điển sử dụng nhiều hoạt động, dùng các ý định để điều hướng giữa các hoạt động và truyền dữ liệu sang ứng dụng khác.

Điều kiện tiên quyết

Bạn phải làm được những việc sau:

  • Thao tác trên một dự án trong Android Studio.
  • Xử lý và thêm tài nguyên XML trong Android Studio.
  • Ghi đè và triển khai phương thức trong một lớp (class) hiện có.
  • Tạo thực thể của lớp Kotlin (Kotlin class), truy cập vào thuộc tính của lớp và gọi phương thức.
  • Tham khảo tài liệu trên developer.android.com để tìm hiểu thêm về các lớp cụ thể.

Kiến thức bạn sẽ học được

Cách:

  • Sử dụng một ý định tường minh (explicit intent) để điều hướng đến một hoạt động cụ thể.
  • Sử dụng một ý định ngầm ẩn (implicit intent) để điều hướng đến nội dung trong một ứng dụng khác.
  • Thêm tuỳ chọn trình đơn để thêm các nút vào thanh ứng dụng.

Sản phẩm bạn sẽ tạo ra

  • Sửa đổi một ứng dụng từ điển để triển khai chức năng điều hướng giữa các màn hình bằng cách sử dụng ý định và thêm trình đơn tuỳ chọn.

Bạn cần có

  • Máy tính đã cài đặt Android Studio.

2. Mã khởi đầu

Trong một số bước tiếp theo, bạn sẽ làm việc với ứng dụng Words (Từ vựng). Words là một ứng dụng từ điển đơn giản, với danh sách các chữ cái, các từ tương ứng với mỗi chữ cái kèm theo khả năng tra cứu định nghĩa của từng từ trong trình duyệt.

Có rất nhiều vấn đề ở đây, nhưng đừng lo lắng, bạn sẽ không phải xây dựng toàn bộ ứng dụng để tìm hiểu về ý định (intent). Thay vào đó, bạn đã được cung cấp một phiên bản chưa hoàn chỉnh của dự án, còn gọi là dự án bắt đầu.

Tuy tất cả các màn hình đều được triển khai nhưng bạn chưa thể điều hướng từ màn hình này sang màn hình khác. Nhiệm vụ của bạn là sử dụng các ý định để làm cho toàn bộ dự án hoạt động ổn thoả mà không phải xây dựng mọi thứ từ đầu.

Tải mã khởi đầu xuống cho lớp học lập trình này

Lớp học lập trình này sẽ cung cấp mã khởi đầu, cho phép bạn mở rộng bằng các tính năng được dạy tại đây. Mã khởi đầu có thể chứa mã bạn đã quen thuộc qua các lớp học lập trình trước đây. Đồng thời cũng có những đoạn mã lạ mà bạn sẽ tìm hiểu trong các lớp học lập trình sau này.

Khi bạn tải mã khởi đầu xuống qua GitHub, hãy lưu ý rằng tên thư mục là android-basics-kotlin-words-app-starter. Chọn thư mục này khi bạn mở dự án trong Android Studio.

Nếu bạn đã quen với các lệnh git, hãy lưu ý rằng mã khởi đầu nằm trong một nhánh tên là "starter" (khởi đầu). Sau khi sao chép repo, hãy xem xét mã trong nhánh origin/starter. Nếu bạn chưa từng sử dụng các lệnh git, hãy làm theo các bước dưới đây để tải mã xuống qua GitHub.

  1. Chuyển đến trang kho lưu trữ GitHub được cung cấp cho dự án.
  2. Xác minh rằng tên nhánh khớp với tên nhánh được chỉ định trong lớp học lập trình. Ví dụ: trong ảnh chụp màn hình sau đây, tên nhánh là main.

1e4c0d2c081a8fd2.png

  1. Trên trang GitHub cho dự án này, nhấp vào nút Code (Mã). Thao tác này sẽ khiến một cửa sổ bật lên.

1debcf330fd04c7b.png

  1. Trong cửa sổ bật lên, nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
  2. Xác định vị trí của tệp trên máy tính (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  3. Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.

Mở dự án trong Android Studio

  1. Khởi động Android Studio.
  2. Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open (Mở).

d8e9dbdeafe9038a.png

Lưu ý: Nếu Android Studio đã mở thì chuyển sang chọn tuỳ chọn File (Tệp) > Open (Mở) trong trình đơn.

8d1fda7396afe8e5.png

  1. Trong trình duyệt tệp, hãy chuyển đến vị trí của thư mục dự án chưa giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  2. Nhấp đúp vào thư mục dự án đó.
  3. Chờ Android Studio mở dự án.
  4. Nhấp vào nút Run (Chạy) 8de56cba7583251f.png để tạo và chạy ứng dụng nhằm đảm bảo rằng ứng dụng được xây dựng như mong đợi.

3. Tổng quan về ứng dụng Words

Trước khi tiếp tục, hãy dành chút thời gian để làm quen với dự án. Bạn nên quen thuộc với tất cả khái niệm có trong bài trước. Hiện tại, ứng dụng này bao gồm hai hoạt động (activity), mỗi hoạt động có một khung hiển thị tái chế (recycler view) và một trình chuyển đổi (adapter).

61af34429128695e.png

Cụ thể, bạn sẽ xử lý những tệp sau đây:

  1. LetterAdapter được RecyclerView sử dụng trong MainActivity. Mỗi chữ cái là một nút (button) với một onClickListener đang trống. Đây là nơi bạn sẽ xử lý các thao tác nhấn nút để điều hướng đến DetailActivity.
  2. WordAdapter được RecyclerView sử dụng trong DetailActivity để hiện danh sách từ vựng. Mặc dù bạn chưa thể điều hướng đến màn hình này, nhưng chỉ cần biết rằng mỗi từ cũng có một nút tương ứng với một onClickListener. Đây là nơi bạn sẽ thêm mã điều hướng đến trình duyệt để hiển thị định nghĩa cho từ.
  3. MainActivity cũng cần đến một vài thay đổi. Đây là nơi bạn sẽ triển khai trình đơn tuỳ chọn (options menu) để hiển thị nút cho phép người dùng chuyển đổi bố cục giữa dạng danh sách (list) và dạng lưới (grid).

ce3474dba2a9c1c8.png

Sau khi đã thấy ổn với dự án này, hãy tiếp tục chuyển đến phần tiếp theo để tìm hiểu về khái niệm ý định.

4. Giới thiệu về ý định

Sau khi thiết lập dự án ban đầu, hãy cùng thảo luận về ý định cũng như cách dùng ý định trong ứng dụng.

Ý định (intent) là một đối tượng đại diện cho một số hành động cần được thực hiện. Cách sử dụng phổ biến nhất (nhưng không phải là duy nhất) của một ý định là để bắt đầu một hoạt động (activity). Có hai loại ý định: ngầm ẩn (implicit) và tường minh (explicit). Ý định tường minh thì rất cụ thể, trong đó bạn biết chính xác hoạt động cần chạy, thường là một màn hình trong ứng dụng.

Ý định ngầm ẩn thì trừu tượng hơn một chút, trong đó bạn cho hệ thống biết kiểu hành động, chẳng hạn như mở một đường liên kết, soạn một email hoặc gọi một cuộc điện thoại và hệ thống sẽ chịu trách nhiệm xác định cách đáp ứng yêu cầu. Có thể bạn đã thấy cả hai kiểu ý định trong thực tế mà không hề hay biết. Nhìn chung, khi hiển thị một hoạt động trong ứng dụng của bạn thì tức là bạn đang sử dụng ý định tường minh.

Tuy nhiên, đối với các hành động không nhất thiết liên quan đến ứng dụng hiện tại — chẳng hạn như bạn tìm thấy một trang tài liệu Android thú vị và muốn chia sẻ trang đó với bạn bè — bạn nên sử dụng ý định ngầm ẩn. Bạn có thể thấy một trình đơn như thế này hỏi bạn xem nên dùng ứng dụng nào để chia sẻ trang.

e9c77033d9224170.png

Bạn dùng một ý định tường minh đối với các hành động hoặc trình bày các màn hình trong ứng dụng, đồng thời chịu trách nhiệm về toàn bộ quá trình. Bạn thường dùng ý định ngầm ẩn để thực hiện các hành động liên quan đến các ứng dụng khác và dựa vào hệ thống để xác định kết quả cuối cùng. Bạn sẽ sử dụng cả hai kiểu ý định này trong ứng dụng Words.

702236c6e2276f91.png

5. Thiết lập ý định tường minh

Đã đến lúc triển khai ý định đầu tiên của bạn. Trên màn hình đầu tiên, khi nhấn vào một chữ cái, người dùng sẽ được chuyển đến một màn hình thứ hai có danh sách từ vựng. DetailActivity đã được triển khai, vì vậy tất cả những gì cần làm là khởi chạy DetailActivity bằng một ý định. Vì ứng dụng của bạn biết chính xác hoạt động nào sẽ được khởi chạy, nên bạn sử dụng một ý định tường minh.

Việc tạo và sử dụng một ý định chỉ cần vài bước:

  1. Mở LetterAdapter.kt rồi cuộn xuống onBindViewHolder(). Bên dưới dòng dùng để đặt văn bản của nút, hãy đặt onClickListener cho holder.button.
holder.button.setOnClickListener {

}
  1. Sau đó, hãy tham chiếu đến context.
val context = holder.itemView.context
  1. Tạo một Intent, truyền ngữ cảnh và tên lớp (class) của hoạt động đích.
val intent = Intent(context, DetailActivity::class.java)

Tên của hoạt động bạn muốn hiển thị được chỉ định bằng DetailActivity::class.java. Một đối tượng DetailActivity thực tế được tạo ngầm.

  1. Gọi phương thức putExtra, truyền "letter" ("chữ cái") vào làm đối số đầu tiên và văn bản của nút làm đối số thứ hai.
intent.putExtra("letter", holder.button.text.toString())

Phần bổ sung (extra) là gì? Hãy nhớ rằng ý định chỉ là một tập hợp lệnh – chưa có thực thể nào cho hoạt động đích. Thay vào đó, phần bổ sung (extra) là một phần dữ liệu, chẳng hạn như số hoặc chuỗi, được đặt tên để truy xuất sau này. Điều này cũng tương tự như việc truyền một đối số khi bạn gọi một hàm. Do một DetailActivity có thể xuất hiện cho chữ cái bất kỳ, nên bạn cần phải cho biết cụ thể chữ cái nào để xuất hiện.

Ngoài ra, tại sao bạn cho rằng cần phải gọi toString()? Văn bản của nút đã là một chuỗi rồi phải không?

Đại khái là thế. Văn bản đó thực ra thuộc kiểu CharSequence, được gọi là giao diện (interface). Hiện tại, bạn chưa cần biết gì về giao diện Kotlin, ngoại trừ việc đó là một cách để đảm bảo một kiểu (type), chẳng hạn như chuỗi (string), triển khai các chức năng và thuộc tính cụ thể. Bạn có thể xem CharSequence là một đại diện chung hơn của một lớp giống như chuỗi. Thuộc tính text của một nút có thể là một chuỗi hoặc có thể là đối tượng bất kỳ, cũng có thể là CharSequence. Tuy nhiên, phương thức putExtra() chấp nhận một String chứ không phải CharSequence bất kỳ, vì vậy bạn cần gọi toString().

  1. Gọi phương thức startActivity() trên đối tượng ngữ cảnh, truyền intent vào.
context.startActivity(intent)

Bây giờ, hãy chạy ứng dụng rồi thử nhấn vào một chữ cái. Màn hình chi tiết xuất hiện! Tuy nhiên, bất kể người dùng nhấn vào chữ cái nào, màn hình chi tiết sẽ luôn cho thấy các từ cho chữ cái A. Bạn vẫn còn một số việc cần làm trong hoạt động chi tiết để ứng dụng cho thấy các từ tương ứng với chữ cái bất kỳ được truyền qua dưới dạng phần bổ sung intent.

6. Thiết lập DetailActivity

Bạn mới tạo ý định tường minh đầu tiên! Tiếp theo, trên màn hình chi tiết.

Trong phương thức onCreate của DetailActivity, sau lệnh gọi đến setContentView, hãy thay thế chữ cái đã mã hoá cứng bằng mã để nhận letterId được truyền đến từ intent.

val letterId = intent?.extras?.getString("letter").toString()

Có rất nhiều vấn đề ở đây, vì vậy, hãy xem xét từng phần:

Trước tiên, thuộc tính intent đến từ đâu? Đây không phải một thuộc tính của DetailActivity mà là thuộc tính của mọi hoạt động. Thuộc tính này sẽ tham chiếu đến ý định dùng để khởi chạy hoạt động.

Thuộc tính bổ sung thuộc kiểu Bundle và chắc hẳn bạn cũng đoán được rằng thuộc tính này mang đến một cách để truy cập tất cả phần bổ sung được truyền vào ý định.

Cả hai thuộc tính này đều được đánh dấu bằng dấu chấm hỏi. Vì sao lại như vậy? Lý do là các thuộc tính intentextras có thể nhận giá trị rỗng, tức là các thuộc tính này có thể có hoặc không có giá trị. Đôi khi, bạn có thể muốn một biến trở thành null. Thuộc tính intent có thể không phải là một Intent (nếu hoạt động không được khởi chạy từ ý định) và thuộc tính bổ sung có thể không phải là Bundle, mà là một giá trị tên là null. Trong Kotlin, null có nghĩa là không có một giá trị. Đối tượng có thể tồn tại hoặc có thể là null. Nếu ứng dụng của bạn cố gắng truy cập vào một thuộc tính hoặc gọi một hàm trên đối tượng null, thì ứng dụng đó sẽ gặp sự cố. Để truy cập an toàn vào giá trị này, bạn nên đặt ? sau tên. Nếu intentnull, ứng dụng của bạn thậm chí sẽ không cố gắng truy cập vào thuộc tính bổ sung và nếu extras nhận giá trị rỗng, mã của bạn thậm chí sẽ không cố gắng gọi getString().

Làm thế nào để bạn biết thuộc tính nào cần dấu chấm hỏi để đảm bảo không có vấn đề? Bạn có thể biết nếu theo sau tên kiểu là dấu chấm hỏi hay dấu chấm than.

b43155b06a5556e.png

Điều cuối cùng cần lưu ý là chữ cái thực tế được truy xuất bằng getString sẽ trả về String?, vì vậy, toString() được gọi để đảm bảo đó là String chứ không phải null.

Bây giờ, khi chạy ứng dụng và điều hướng đến màn hình chi tiết, bạn sẽ thấy danh sách từ vựng cho từng chữ cái.

c465ef280fe3792a.png

Dọn dẹp mã

Cả mã để thực hiện ý định và mã truy xuất chữ cái đã chọn đều mã hoá cứng tên của extra, "letter" ("chữ cái"). Mặc dù cách này phù hợp với ví dụ nhỏ này, nhưng đây không phải là phương pháp hay nhất đối với những ứng dụng lớn mà bạn có nhiều ý định bổ sung cần theo dõi.

Mặc dù bạn có thể chỉ cần tạo một hằng số có tên là "letter", nhưng việc này có thể gặp khó khăn khi bạn thêm nhiều ý định bổ sung vào ứng dụng. Bạn sẽ đặt hằng số này ở lớp nào? Hãy nhớ rằng kiểu chuỗi này được dùng trong cả DetailActivityMainActivity. Bạn cần có cách để định nghĩa một hằng số (constant) để có thể sử dụng trên nhiều lớp (class), trong khi vẫn giữ cho mã của bạn luôn được ngăn nắp.

Rất may là có một tính năng Kotlin tiện dụng tên là đối tượng đồng hành (companion object). Tính năng này có thể dùng để phân tách các hằng số và giúp bạn dùng các hằng số đó mà không cần sử dụng một thực thể cụ thể của lớp. Đối tượng đồng hành tương tự như các đối tượng khác, chẳng hạn như các thực thể của một lớp. Tuy nhiên, chỉ một thực thể duy nhất của đối tượng đồng hành sẽ tồn tại trong thời lượng của chương trình, đó là lý do đôi khi mô hình này được gọi là mẫu singleton. Tuy có rất nhiều trường hợp sử dụng singleton nằm ngoài phạm vi lớp học lập trình này, nhưng hiện tại, bạn sẽ dùng một đối tượng đồng hành như một cách để sắp xếp các hằng số và cho phép truy cập vào các đối tượng đó bên ngoài DetailActivity. Bạn sẽ bắt đầu bằng cách sử dụng một đối tượng đồng hành để tái cấu trúc mã cho phần phụ "letter" ("chữ cái").

  1. Trong DetailActivity, ngay phía trên onCreate, hãy thêm đoạn mã sau đây:
companion object {

}

Hãy lưu ý rằng cách này tương tự như việc định nghĩa một lớp, ngoại trừ việc bạn sử dụng từ khoá object. Cũng có một từ khoá companion, có nghĩa là đối tượng đó được liên kết với lớp DetailActivity và không cần phải đặt tên kiểu riêng biệt cho đối tượng đó.

  1. Trong các dấu ngoặc nhọn, hãy thêm một thuộc tính dành cho hằng số chữ cái.
const val LETTER = "letter"
  1. Để sử dụng hằng số mới, hãy cập nhật lệnh gọi chữ cái mã hoá cứng trong onCreate() như sau:
val letterId = intent?.extras?.getString(LETTER).toString()

Xin nhắc lại rằng bạn có thể tham chiếu đến đối tượng bằng ký hiệu dấu chấm như bình thường, nhưng hằng số sẽ thuộc về DetailActivity.

  1. Chuyển sang LetterAdapter rồi sửa đổi lệnh gọi thành putExtra để sử dụng hằng số mới.
intent.putExtra(DetailActivity.LETTER, holder.button.text.toString())

Đã hoàn tất! Bằng cách tái cấu trúc, bạn đã giúp cho mã dễ đọc và dễ duy trì hơn. Nếu hằng số này (hoặc hằng số bất kỳ mà bạn thêm vào) cần phải thay đổi, thì bạn chỉ cần thực hiện thay đổi đó ở một nơi.

Để tìm hiểu thêm về đối tượng đồng hành, hãy xem tài liệu của Kotlin về Biểu thức và khai báo đối tượng.

7. Thiết lập ý định ngầm ẩn

Trong hầu hết trường hợp, bạn sẽ trình bày các hoạt động cụ thể từ ứng dụng của mình. Tuy nhiên, trong một số trường hợp, có thể bạn không biết nên khởi chạy hoạt động hoặc ứng dụng nào. Trên màn hình chi tiết của chúng ta, mỗi từ là một nút cho thấy định nghĩa người dùng dành cho từ đó.

Trong ví dụ của chúng ta, bạn sẽ sử dụng chức năng từ điển do Google Tìm kiếm cung cấp. Tuy nhiên, thay vì thêm hoạt động mới vào ứng dụng, bạn sẽ khởi chạy trình duyệt của thiết bị để cho thấy trang tìm kiếm.

Vậy, có lẽ bạn cần đến một ý định tải trang trong Chrome – trình duyệt mặc định trên Android?

Chưa đúng.

Có thể một số người dùng thích trình duyệt bên thứ ba hơn. Hoặc điện thoại của họ có trình duyệt được nhà sản xuất cài đặt sẵn. Có thể họ đã cài đặt ứng dụng tìm kiếm của Google hoặc thậm chí là ứng dụng từ điển của bên thứ ba.

Bạn không thể biết chắc người dùng đã cài đặt những ứng dụng nào. Bạn cũng không nên giả định về cách họ có thể muốn tra cứu một từ. Đây là một ví dụ hoàn hảo về thời điểm sử dụng ý định ngầm ẩn. Ứng dụng của bạn cung cấp thông tin cho hệ thống về hành động cần thực hiện và hệ thống sẽ chỉ ra việc cần làm với hành động đó, nhắc người dùng cung cấp thêm thông tin nếu cần.

Hãy làm như sau để tạo ý định ngầm ẩn:

  1. Đối với ứng dụng này, bạn sẽ tìm kiếm một từ trên Google. Kết quả tìm kiếm đầu tiên sẽ là định nghĩa theo từ điển của từ đó. Vì cùng một URL cơ sở được sử dụng cho mọi lượt tìm kiếm, bạn nên khai báo URL này làm hằng số riêng. Trong DetailActivity, hãy sửa đổi đối tượng đồng hành để thêm hằng số mới, SEARCH_PREFIX. Đây là URL cơ sở dành cho một lượt tìm kiếm trên Google.
companion object {
   const val LETTER = "letter"
   const val SEARCH_PREFIX = "https://www.google.com/search?q="
}
  1. Sau đó, mở WordAdapter và trong phương thức onBindViewHolder(), hãy gọi setOnClickListener() trên nút. Hãy bắt đầu bằng cách tạo một Uri dành cho cụm từ tìm kiếm. Khi gọi parse() để tạo một Uri từ một String, bạn cần sử dụng định dạng chuỗi để thêm từ vào SEARCH_PREFIX.
holder.button.setOnClickListener {
    val queryUrl: Uri = Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}")
}

Nếu bạn đang thắc mắc URI là gì, thì đó không phải là lỗi chính tả, mà là viết tắt của Uniform Resource Identifier (Mã tài nguyên đồng nhất). Bạn có thể đã biết rằng một URL, hay Uniform Resource Locator (Bộ định vị tài nguyên đồng nhất), là một chuỗi trỏ đến một trang web. URI là một thuật ngữ chung hơn cho định dạng đó. Tất cả URL đều là URI, nhưng không phải tất cả URI đều là URL. Các URI khác, chẳng hạn như địa chỉ cho số điện thoại, sẽ bắt đầu bằng tel:, nhưng đây được coi là URN hoặc Uniform Resource Name (Tên tài nguyên đồng nhất), thay vì một URL. Kiểu dữ liệu dùng để đại diện cho cả hai được gọi là URI.

828cef3fdcfdaed.png

Lưu ý rằng không có tham chiếu đến hoạt động nào trong ứng dụng của bạn tại đây. Bạn chỉ cần cung cấp một URI, mà không nêu rõ cách sử dụng cuối cùng.

  1. Sau khi khai báo queryUrl, hãy khởi chạy đối tượng intent mới:
val intent = Intent(Intent.ACTION_VIEW, queryUrl)

Thay vì ngữ cảnh và hoạt động, bạn truyền Intent.ACTION_VIEW vào cùng với URI.

ACTION_VIEW là một ý định chung lấy URI (trong trường hợp của bạn là địa chỉ web). Sau đó, hệ thống sẽ biết xử lý ý định này bằng cách mở URI trong trình duyệt web của người dùng. Có thể kể đến một số kiểu ý định khác như sau:

  • CATEGORY_APP_MAPS – mở ứng dụng bản đồ
  • CATEGORY_APP_EMAIL – mở ứng dụng email
  • CATEGORY_APP_GALLERY – mở ứng dụng thư viện (ảnh)
  • ACTION_SET_ALARM – đặt báo thức ở chế độ nền
  • ACTION_DIAL – bắt đầu một cuộc gọi điện thoại

Để tìm hiểu thêm, hãy xem tài liệu về một số ý định phổ biến.

  1. Cuối cùng, mặc dù không chạy hoạt động cụ thể nào trong ứng dụng, nhưng bạn đang yêu cầu hệ thống mở một ứng dụng khác bằng cách gọi startActivity() và truyền vào intent.
context.startActivity(intent)

Giờ đây, khi bạn chạy ứng dụng, hãy di chuyển đến danh sách từ vựng và nhấn vào một trong các từ, thiết bị của bạn sẽ chuyển đến URL (hoặc trình bày một danh sách tuỳ chọn, tuỳ thuộc vào các ứng dụng đã cài đặt).

Hành vi chính xác sẽ còn tuỳ thuộc vào người dùng, mang lại trải nghiệm liền mạch cho mọi người mà không làm phức tạp mã của bạn.

8. Thiết lập trình đơn và biểu tượng

Giờ đây, khi bạn đã làm cho ứng dụng có thể điều hướng toàn bộ bằng cách thêm ý định rõ ràng và ngầm ẩn, đã đến lúc thêm tuỳ chọn trình đơn để người dùng có thể chuyển đổi giữa bố cục dạng danh sách và lưới dành cho các chữ cái.

Giờ đây, có thể bạn đã nhận thấy nhiều ứng dụng có thanh này ở đầu màn hình. Đây được gọi là thanh ứng dụng. Ngoài việc cho thấy tên ứng dụng, thanh ứng dụng có thể được tuỳ chỉnh và lưu trữ nhiều chức năng hữu ích, chẳng hạn như lối tắt cho các thao tác hữu ích hoặc trình đơn mục bổ sung.

dfc4095251c1466e.png

Đối với ứng dụng này, mặc dù chúng tôi không thêm trình đơn hoàn chỉnh, nhưng bạn sẽ tìm hiểu cách thêm nút tuỳ chỉnh vào thanh ứng dụng để người dùng có thể thay đổi bố cục.

  1. Trước tiên, bạn cần nhập hai biểu tượng để biểu thị chế độ xem dạng lưới và danh sách. Thêm thành phần vectơ hình mẫu (clip art vector) tên là "view module" (đặt tên là ic_grid_ layout) và "view list" (đặt tên là ic_linear_layout). Nếu bạn cần xem lại cách thêm biểu tượng Material, hãy xem hướng dẫn trên trang này.

5a01fc03113ac399.png

  1. Bạn cũng cần biết cách cho hệ thống biết các tuỳ chọn nào hiển thị trong thanh ứng dụng và biểu tượng nào cần sử dụng. Để làm điều này, hãy thêm tệp tài nguyên mới bằng cách nhấp chuột phải vào thư mục res rồi chọn New > Android Resource File (Mới > Tệp tài nguyên Android). Đặt Resource type (Loại tài nguyên) thành MenuFile name (Tên tệp) thành layout_menu.

c4f83806a1aa121b.png

  1. Nhấp vào OK.
  2. Mở res/trình đơn/layout_menu. Thay thế nội dung của layout_menu.xml bằng nội dung sau:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item android:id="@+id/action_switch_layout"
       android:title="@string/action_switch_layout"
       android:icon="@drawable/ic_linear_layout"
       app:showAsAction="always" />
</menu>

Cấu trúc của tệp trình đơn khá đơn giản. Cũng giống như bố cục bắt đầu bằng trình quản lý bố cục để giữ riêng từng chế độ xem, tệp xml menu bắt đầu bằng thẻ trình đơn, trong đó có các tuỳ chọn riêng biệt.

Trình đơn của bạn chỉ có một nút với một vài thuộc tính:

  • id: Cũng giống như khung hiển thị (view), tuỳ chọn trình đơn có một mã nhận dạng để có thể tham chiếu trong mã.
  • title: Văn bản này sẽ không xuất hiện trong trường hợp của bạn, nhưng có thể hữu ích cho các trình đọc màn hình (screen reader) để xác định trình đơn
  • icon: Mặc định là ic_linear_layout. Tuy nhiên, thao tác này sẽ ở trạng thái bật và tắt để hiển thị biểu tượng lưới, khi bạn chọn nút này.
  • showAsAction: cho hệ thống biết cách hiển thị nút này. Do bạn đã đặt thành luôn bật, nên nút này sẽ luôn hiển thị trên thanh ứng dụng và không trở thành một phần của trình đơn mục bổ sung.

Tất nhiên, việc chỉ thiết lập các thuộc tính không có nghĩa là trình đơn sẽ thực hiện điều gì đó.

Bạn vẫn cần thêm một số mã trong MainActivity.kt để trình đơn hoạt động được.

9. Triển khai nút Trình đơn

Để nút trình đơn hoạt động, bạn có thể làm một số việc trong MainActivity.kt.

  1. Trước tiên, bạn nên tạo một thuộc tính để theo dõi trạng thái bố cục của ứng dụng. Thao tác này sẽ giúp bạn dễ dàng chuyển đổi nút bố cục. Đặt giá trị mặc định thành true, vì trình quản lý bố cục tuyến tính sẽ được sử dụng theo mặc định.
private var isLinearLayoutManager = true
  1. Khi người dùng bật/tắt nút, bạn muốn danh sách các mục chuyển thành một lưới bao gồm các mục. Hãy nhớ lại khi bạn tìm hiểu về các khung hiển thị tái chế (recycler view), bạn sẽ thấy có nhiều trình quản lý bố cục, một trong số đó là GridLayoutManager cho phép quản lý nhiều mục trên một hàng.
private fun chooseLayout() {
    if (isLinearLayoutManager) {
        recyclerView.layoutManager = LinearLayoutManager(this)
    } else {
        recyclerView.layoutManager = GridLayoutManager(this, 4)
    }
    recyclerView.adapter = LetterAdapter()
}

Ở đây, bạn sử dụng một câu lệnh if để chỉ định trình quản lý bố cục. Ngoài việc đặt layoutManager, mã này còn gán trình chuyển đổi (adapter) LetterAdapter được dùng cho cả bố cục danh sách và bố cục lưới.

  1. Khi ban đầu bạn thiết lập trình đơn trong xml, bạn đã cấp cho trình đơn đó một biểu tượng tĩnh. Tuy nhiên, sau khi chuyển đổi bố cục, bạn nên cập nhật biểu tượng để phản ánh chức năng mới — chuyển về bố cục danh sách. Tại đây, bạn chỉ cần đặt biểu tượng bố cục tuyến tính và lưới, dựa trên bố cục mà nút sẽ chuyển về vào lần nhấn kế tiếp.
private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   // Set the drawable for the menu icon based on which LayoutManager is currently in use

   // An if-clause can be used on the right side of an assignment if all paths return a value.
   // The following code is equivalent to
   // if (isLinearLayoutManager)
   //     menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
   // else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
}

Biểu tượng này được đặt theo điều kiện và dựa trên thuộc tính isLinearLayoutManager.

Để ứng dụng của bạn thực sự dùng được trình đơn, bạn cần ghi đè hai phương thức khác.

  • onCreateOptionsMenu: nơi bạn mở rộng trình đơn tuỳ chọn và thực hiện các bước thiết lập bổ sung
  • onOptionsItemSelected: nơi bạn sẽ thực sự gọi đến chooseLayout() khi nút này được chọn.
  1. Ghi đè onCreateOptionsMenu như sau:
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   menuInflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu?.findItem(R.id.action_switch_layout)
   // Calls code to set the icon based on the LinearLayoutManager of the RecyclerView
   setIcon(layoutButton)

   return true
}

Không có gì hào nhoáng ở đây. Sau khi mở rộng bố cục, bạn gọi setIcon() để đảm bảo biểu tượng đã đúng, dựa trên bố cục. Phương thức này trả về Boolean — bạn trả về true tại đây vì bạn muốn tạo trình đơn tuỳ chọn.

  1. Triển khai onOptionsItemSelected như phần dưới đây, chỉ với một vài dòng mã.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           // Sets isLinearLayoutManager (a Boolean) to the opposite value
           isLinearLayoutManager = !isLinearLayoutManager
           // Sets layout and icon
           chooseLayout()
           setIcon(item)

           return true
       }
       //  Otherwise, do nothing and use the core event handling

       // when clauses require that all possible paths be accounted for explicitly,
       //  for instance both the true and false cases if the value is a Boolean,
       //  or an else to catch all unhandled cases.
       else -> super.onOptionsItemSelected(item)
   }
}

Đoạn mã này được gọi mỗi khi nào nhấn vào một mục trong trình đơn. Vì vậy, bạn cần kiểm tra xem mục nào được nhấn. Bạn sử dụng câu lệnh when ở trên. Nếu id khớp với mục trong trình đơn action_switch_layout, thì bạn sẽ phủ định giá trị của isLinearLayoutManager. Sau đó, hãy gọi chooseLayout()setIcon() để cập nhật giao diện người dùng cho phù hợp.

Còn một điều nữa trước khi bạn chạy ứng dụng. Vì trình quản lý bố cục và trình chuyển đổi hiện đã được đặt trong chooseLayout(), bạn phải thay thế mã đó trong onCreate() để gọi phương thức mới. Sau khi thay đổi, onCreate() sẽ có dạng như sau.

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)

   recyclerView = binding.recyclerView
   // Sets the LinearLayoutManager of the recyclerview
   chooseLayout()
}

Bây giờ, hãy chạy ứng dụng và bạn có thể chuyển đổi giữa chế độ xem danh sách và lưới bằng nút trình đơn.

10. Mã giải pháp

Mã giải pháp dành cho lớp học lập trình này nằm trong dự án dưới đây:

  1. Chuyển đến trang kho lưu trữ GitHub được cung cấp cho dự án.
  2. Xác minh rằng tên nhánh khớp với tên nhánh được chỉ định trong lớp học lập trình. Ví dụ: trong ảnh chụp màn hình sau đây, tên nhánh là main.

1e4c0d2c081a8fd2.png

  1. Trên trang GitHub cho dự án này, nhấp vào nút Code (Mã). Thao tác này sẽ khiến một cửa sổ bật lên.

1debcf330fd04c7b.png

  1. Trong cửa sổ bật lên, nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
  2. Xác định vị trí của tệp trên máy tính (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  3. Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.

Mở dự án trong Android Studio

  1. Khởi động Android Studio.
  2. Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open (Mở).

d8e9dbdeafe9038a.png

Lưu ý: Nếu Android Studio đã mở thì chuyển sang chọn tuỳ chọn File (Tệp) > Open (Mở) trong trình đơn.

8d1fda7396afe8e5.png

  1. Trong trình duyệt tệp, hãy chuyển đến vị trí của thư mục dự án chưa giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  2. Nhấp đúp vào thư mục dự án đó.
  3. Chờ Android Studio mở dự án.
  4. Nhấp vào nút Run (Chạy) 8de56cba7583251f.png để tạo bản dựng và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.

11. Tóm tắt

  • Ý định tường minh được dùng để điều hướng đến hoạt động cụ thể trong ứng dụng.
  • Ý định ngầm ẩn tương ứng với hành động cụ thể (như mở đường liên kết hoặc chia sẻ hình ảnh) và cho phép hệ thống xác định cách thực hiện ý định.
  • Tuỳ chọn trình đơn cho phép bạn thêm các nút và trình đơn vào thanh ứng dụng.
  • Đối tượng đồng hành đưa ra cách liên kết hằng số có thể sử dụng lại với một kiểu, thay vì sử dụng một thực thể của kiểu đối tượng đó.

Cách thực hiện một ý định:

  • Tạo một tham chiếu đến ngữ cảnh.
  • Tạo một đối tượng Intent để cung cấp kiểu hoạt động hoặc kiểu ý định (tuỳ thuộc vào việc đó là tường minh hay ngầm ẩn).
  • Truyền mọi dữ liệu cần thiết bằng cách gọi putExtra().
  • Gọi startActivity() truyền vào đối tượng intent.

12. Tìm hiểu thêm