Mảnh và thành phần điều hướng

1. Trước khi bắt đầu

Trong lớp học lập trình về Hoạt động và Ý định, bạn đã thêm ý định trong ứng dụng Words, để di chuyển giữa hai hoạt động. Đây là một mẫu điều hướng rất hữu ích, nhưng cũng chỉ là một phần trong quá trình xây dựng giao diện người dùng động cho ứng dụng. Nhiều ứng dụng Android không cần một hoạt động riêng biệt trên mỗi màn hình. Trên thực tế, nhiều mẫu giao diện người dùng phổ biến (chẳng hạn như các thẻ) tồn tại trong một hoạt động duy nhất thông qua việc sử dụng các mảnh.

64100b59bb487856.png

Mảnh (fragment) là một phần giao diện người dùng có thể sử dụng lại và nhúng vào một hoặc nhiều hoạt động. Trong ảnh chụp màn hình ở trên, việc nhấn vào một thẻ sẽ không kích hoạt ý định hiển thị màn hình tiếp theo. Thay vào đó, việc chuyển đổi các thẻ sẽ chỉ hoán đổi mảnh trước đó với một mảnh khác. Những thao tác này xảy ra mà không khởi động một hoạt động khác.

Thậm chí, bạn có thể hiển thị đồng thời nhiều mảnh trên một màn hình, chẳng hạn như bố cục chi tiết/tổng thể cho thiết bị máy tính bảng. Trong ví dụ dưới đây, cả phần giao diện điều hướng ở bên trái và nội dung ở bên phải đều được chứa trong một mảnh riêng biệt. Cả hai mảnh tồn tại đồng thời trong cùng một hoạt động.

b5711344c5795d55.png

Như bạn thấy, mảnh là một phần không thể thiếu để xây dựng các ứng dụng chất lượng cao. Trong lớp học lập trình này, bạn sẽ tìm hiểu kiến thức cơ bản về các mảnh và chuyển đổi ứng dụng Words để sử dụng tính năng này. Bạn cũng sẽ tìm hiểu cách sử dụng thành phần điều hướng (Navigation component) của Jetpack và xử lý một tệp tài nguyên mới có tên Navigation Graph (Biểu đồ điều hướng) để di chuyển giữa các mảnh trong cùng một hoạt động lưu trữ. Kết thúc lớp học lập trình này, bạn sẽ áp dụng những kỹ năng cơ bản đã học để triển khai các mảnh trong ứng dụng tiếp theo.

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

Trước khi tham gia lớp học lập trình này, bạn cần biết

  • Cách thêm tệp tài nguyên XML và tệp Kotlin vào dự án Android Studio.
  • Tổng quan về vòng đời của một hoạt động.
  • Cách ghi đè và triển khai phương thức trong một lớp (class) hiện có.
  • Cách tạo thực thể lớp Kotlin, truy cập vào các thuộc tính lớp và gọi các phương thức.
  • Các khái niệm cơ bản về giá trị có tính chất rỗng (null) và khác rỗng, đồng thời biết cách xử lý an toàn các giá trị rỗng này.

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

  • Sự khác biệt giữa vòng đời mảnh (fragment lifecycle) và vòng đời hoạt động (activity lifecycle).
  • Cách chuyển đổi một hoạt động hiện có thành một mảnh.
  • Cách thêm đích đến vào biểu đồ điều hướng và truyền dữ liệu giữa các mảnh khi sử dụng trình bổ trợ Safe Args.

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

  • Bạn sẽ chỉnh sửa ứng dụng Words để sử dụng một hoạt động duy nhất và nhiều mảnh, đồng thời di chuyển giữa các mảnh sử dụng thành phần điều hướng (Navigation Component).

Bạn cần có

  • Một máy tính đã cài đặt Android Studio.
  • Mã giải pháp của ứng dụng Words trong lớp học lập trình về hoạt động và ý định

2. Mã khởi động

Trong lớp học lập trình này, bạn sẽ tiếp tục xử lý ứng dụng Words trong lớp học lập trình về hoạt động và ý định. Nếu bạn đã hoàn thành lớp học lập trình về hoạt động và ý định, hãy dùng mã của bạn làm điểm khởi đầu. Bạn cũng có thể tải mã xuống qua GitHub.

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.

Nếu bạn sử dụng mã khởi đầu lấy trên GitHub, hãy lưu ý tên thư mục là android-basics-kotlin-words-app-activities. Chọn thư mục này khi bạn mở dự án trong Android Studio.

  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. Mảnh và vòng đời mảnh

Mảnh (fragment) đơn giản là một phần có thể sử dụng lại trong giao diện người dùng của ứng dụng. Giống như hoạt động (activity), các mảnh cũng có vòng đời và có thể phản hồi hoạt động đầu vào của người dùng. Mảnh luôn nằm trong hệ phân cấp thành phần hiển thị của một hoạt động khi xuất hiện trên màn hình. Do chú trọng vào khả năng tái sử dụng và mô-đun hoá, mỗi hoạt động có thể đồng thời lưu trữ nhiều mảnh. Mỗi mảnh quản lý một vòng đời riêng.

Vòng đời mảnh

Tương tự như hoạt động, các mảnh được khởi động và xoá khỏi bộ nhớ trong suốt quá trình tồn tại, xuất hiện, biến mất rồi xuất hiện lại trên màn hình. Ngoài ra, cũng giống như hoạt động, vòng đời của mỗi mảnh lại có một số trạng thái tương ứng. Đồng thời, mỗi mảnh lại đưa ra nhiều phương thức ghi đè để phản hồi quá trình chuyển đổi giữa các trạng thái. Vòng đời của mảnh có 5 trạng thái, được biểu hiện dưới dạng enum Lifecycle.State.

  • INITIALIZED (KHỞI ĐỘNG): Một thực thể mới của mảnh đã được tạo.
  • CREATED (TẠO): Phương thức của vòng đời mảnh đầu tiên được gọi. Trong trạng thái này, thành phần hiển thị được liên kết với mảnh này cũng được tạo lập.
  • STARTED (BẮT ĐẦU): Mảnh này xuất hiện trên màn hình nhưng không có "tâm điểm" ("focus"), tức là mảnh này không thể phản hồi hoạt động đầu vào của người dùng.
  • RESUMED (TIẾP TỤC): Mảnh này đang xuất hiện và có tâm điểm.
  • DESTROYED (HUỶ): Đối tượng mảnh này đã bị huỷ tạo.

Tương tự như các hoạt động, lớp Fragment cũng cung cấp nhiều phương thức ghi đè để phản hồi các sự kiện trong vòng đời.

  • onCreate(): Đã tạo biểu hiện của mảnh và mang trạng thái CREATED. Tuy nhiên, thành phần hiển thị (view) tương ứng chưa được tạo.
  • onCreateView(): Đây là phương thức để tăng cường bố cục. Mảnh này đã chuyển sang trạng thái CREATED.
  • onViewCreated(): Phương thức này được gọi sau khi thành phần hiển thị được tạo lập. Trong phương thức này, thường thì bạn liên kết thành phần hiển thị cụ thể với thuộc tính bằng cách gọi đến findViewById().
  • onStart(): Mảnh này đã chuyển sang trạng thái STARTED.
  • onResume(): Mảnh này đã chuyển sang trạng thái RESUMED và có tâm điểm (có thể phản hồi hoạt động đầu vào của người dùng).
  • onPause(): Mảnh này đã trở lại trạng thái STARTED. Người dùng nhìn thấy giao diện người dùng này.
  • onStop(): Mảnh này đã trở lại trạng thái CREATED. Thực thể của đối tượng được tạo lập nhưng không còn hiện trên màn hình.
  • onDestroyView(): Được gọi ngay trước khi mảnh chuyển sang trạng thái DESTROYED. Khung hiển thị đã bị xoá khỏi bộ nhớ nhưng đối tượng mảnh vẫn tồn tại.
  • onDestroy(): Mảnh này chuyển sang trạng thái DESTROYED.

Biểu đồ dưới đây tóm tắt vòng đời của một mảnh cũng như quá trình chuyển đổi giữa các trạng thái.

8dc30a4c12ab71b.png

Các trạng thái và phương thức gọi lại của vòng đời khá giống với trạng thái và phương thức dùng cho các hoạt động. Tuy nhiên, hãy lưu ý sự khác biệt trong phương thức onCreate(). Với các hoạt động, bạn sẽ sử dụng phương thức này để tăng cường bố cục và liên kết thành phần hiển thị. Tuy nhiên, trong vòng đời mảnh, onCreate() được gọi trước khi tạo thành phần hiển thị. Do đó, bạn không thể tăng cường bố cục ở đây. Thay vào đó, bạn sẽ thực hiện việc này trong onCreateView(). Sau khi khung hiển thị đã được tạo, phương thức onViewCreated() sẽ được gọi. Tại đây, bạn có thể liên kết thuộc tính với khung hiển thị cụ thể.

Nghe hơi lý thuyết, nhưng giờ đây bạn đã nắm được các khái niệm cơ bản về cách thức hoạt động của mảnh cũng như điểm giống và khác giữa mảnh và hoạt động. Trong phần còn lại của lớp học lập trình này, bạn sẽ vận dụng kiến thức đó. Trước hết, bạn sẽ di chuyển ứng dụng Words mà bạn từng xử lý để sử dụng bố cục theo mảnh. Sau đó, bạn sẽ triển khai việc di chuyển giữa các mảnh trong một hoạt động.

4. Tạo mảnh và tệp bố cục

Giống như các hoạt động, mỗi mảnh thêm vào sẽ bao gồm hai tệp — một tệp XML cho bố cục và một lớp Kotlin để hiện dữ liệu và xử lý tương tác của người dùng. Bạn sẽ thêm một mảnh cho cả danh sách chữ cái và danh sách từ.

  1. Với app được chọn trong Project Navigator (Trình điều hướng dự án), hãy thêm các mảnh sau (File (Tệp) > New (Mới) > Fragment (Mảnh) > Fragment (Blank) (Mảnh (Trống))), đồng thời cả tệp bố cục và lớp đều được tạo cho từng mảnh.
  • Đối với mảnh đầu tiên, hãy đặt Fragment Name (Tên mảnh) thành LetterListFragment. Fragment Layout Name (Tên bố cục mảnh) nên điền sẵn fragment_letter_list.

4a1729f01d62e65e.png

  • Đối với mảnh thứ hai, hãy đặt Fragment Name (Tên mảnh) thành WordListFragment. Fragment Layout Name (Tên bố cục mảnh) nên điền sẵn fragment_word_list.xml.

5b86ff3a94833b5a.png

  1. Các lớp Kotlin được tạo cho cả hai mảnh sẽ chứa nhiều mã nguyên mẫu thường dùng khi triển khai các mảnh. Tuy nhiên, đây là lần đầu tiên bạn tìm hiểu về các mảnh, nên hãy xoá mọi thứ khỏi hai tệp này, ngoại trừ phần khai báo lớp cho LetterListFragmentWordListFragment. Chúng tôi sẽ hướng dẫn bạn cách triển khai các mảnh từ đầu để bạn biết cách thức hoạt động của toàn bộ mã. Sau khi xoá mã nguyên mẫu, tệp Kotlin sẽ có dạng như sau.

LetterListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class LetterListFragment : Fragment() {

}

WordListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class WordListFragment : Fragment() {

}
  1. Sao chép nội dung của activity_main.xml vào fragment_letter_list.xml và nội dung của activity_detail.xml vào fragment_word_list.xml. Cập nhật tools:context trong fragment_letter_list.xml thành .LetterListFragmenttools:context trong fragment_word_list.xml thành .WordListFragment.

Sau khi thay đổi, tệp bố cục mảnh sẽ có dạng như sau.

fragment_letter_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".LetterListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp" />

</FrameLayout>

fragment_word_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".WordListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp"
       tools:listitem="@layout/item_view" />

</FrameLayout>

5. Triển khai LetterListFragment

Cũng như các hoạt động, bạn cần phải tăng cường bố cục và ràng buộc từng thành phần hiển thị riêng lẻ. Chỉ có một số khác biệt nhỏ khi xử lý vòng đời mảnh. Chúng tôi sẽ hướng dẫn bạn quy trình thiết lập LetterListFragment, sau đó bạn sẽ thực hiện tương tự cho WordListFragment.

Để liên kết thành phần hiển thị trong LetterListFragment, trước tiên, bạn cần có một tham chiếu rỗng đến FragmentLetterListBinding. Android Studio tạo ra những lớp liên kết như thế này cho từng tệp bố cục khi bạn bật thuộc tính viewBinding trong phần buildFeatures của tệp build.gradle. Bạn chỉ cần gán thuộc tính trong lớp mảnh của mình cho mỗi thành phần hiển thị trong FragmentLetterListBinding.

Kiểu phải là FragmentLetterListBinding? và phải có giá trị ban đầu là null. Tại sao giá trị ban đầu phải rỗng? Lý do là bạn không thể tăng cường bố cục cho đến khi gọi onCreateView(). Có một khoảng thời gian giữa thời điểm thực thể LetterListFragment được tạo (khi vòng đời của thực thể bắt đầu với phương thức onCreate()) và thời điểm thuộc tính này thực sự hữu dụng. Ngoài ra, hãy lưu ý rằng bạn có thể tạo và huỷ các thành phần hiển thị của mảnh nhiều lần trong suốt thời gian hoạt động của mảnh đó. Vì lý do đó, bạn cũng cần đặt lại giá trị này trong một phương thức vòng đời khác là onDestroyView().

  1. Trong LetterListFragment.kt, hãy bắt đầu bằng cách lấy tham chiếu đến FragmentLetterListBinding rồi đặt tên tham chiếu đó thành _binding.
private var _binding: FragmentLetterListBinding? = null

Vì thuộc tính này có giá trị rỗng, nên mỗi khi truy cập vào một thuộc tính của _binding, (ví dụ: _binding?.someView), bạn cần thêm toán tử ? để đảm bảo cơ chế an toàn rỗng. Tuy nhiên, điều đó không có nghĩa là bạn tuỳ tiện thêm dấu chấm hỏi vào mã chỉ vì một giá trị rỗng. Nếu chắc chắn sẽ truy cập một giá trị khác rỗng, bạn có thể thêm !! vào tên kiểu. Sau đó, bạn có thể truy cập thuộc tính này như mọi thuộc tính khác mà không cần dùng toán tử ?.

  1. Tạo một thuộc tính mới, có tên là binding (không có dấu gạch dưới) và đặt thuộc tính đó bằng _binding!!.
private val binding get() = _binding!!

Ở đây, get() có nghĩa là thuộc tính này "chỉ nhận" (get-only). Tức là bạn có thể nhận giá trị, nhưng sau khi được gán (như ở đây), bạn không thể gán giá trị đó cho thứ gì khác.

  1. Để hiện trình đơn tuỳ chọn, hãy ghi đè phương thức onCreate(). Bên trong onCreate(), hãy gọi đến phương thức setHasOptionsMenu() với giá trị truyền vào là true.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setHasOptionsMenu(true)
}
  1. Hãy nhớ rằng với các mảnh, bố cục sẽ tăng cường trong onCreateView(). Triển khai onCreateView() bằng cách tăng cường thành phần hiển thị, thiết lập giá trị của _binding và trả về thành phần hiển thị gốc.
override fun onCreateView(
   inflater: LayoutInflater, container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentLetterListBinding.inflate(inflater, container, false)
   val view = binding.root
   return view
}
  1. Bên dưới thuộc tính binding, hãy tạo một thuộc tính cho khung hiển thị tuần hoàn (recycler view).
private lateinit var recyclerView: RecyclerView
  1. Sau đó, thiết lập giá trị của thuộc tính recyclerView trong onViewCreated() rồi gọi chooseLayout() như bạn đã làm trong MainActivity. Bạn sẽ chuyển phương thức chooseLayout() sang LetterListFragment trong chốc lát. Vì vậy, đừng lo lắng khi xảy ra lỗi.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   recyclerView = binding.recyclerView
   chooseLayout()
}

Hãy để ý đến cách thức lớp liên kết này tạo một thuộc tính cho recyclerView và bạn không cần gọi findViewById() cho mỗi thành phần hiển thị.

  1. Cuối cùng, trong onDestroyView(), hãy đặt lại thuộc tính _binding thành null, vì thành phần hiển thị này không còn tồn tại nữa.
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. Điểm duy nhất cần lưu ý là có một số khác biệt nhỏ trong phương thức onCreateOptionsMenu() khi sử dụng các mảnh. Mặc dù lớp Activity có một thuộc tính toàn cục có tên là menuInflater, nhưng Fragment không có thuộc tính này. Thay vào đó, trình tăng cường trình đơn sẽ được truyền vào onCreateOptionsMenu(). Ngoài ra, hãy lưu ý rằng phương thức onCreateOptionsMenu() được dùng với các mảnh không yêu cầu câu lệnh trả về. Triển khai phương thức này như sau:
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
   inflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu.findItem(R.id.action_switch_layout)
   setIcon(layoutButton)
}
  1. Di chuyển toàn bộ phần mã còn lại cho chooseLayout(), setIcon()onOptionsItemSelected() từ MainActivity. Điểm khác biệt duy nhất cần lưu ý là không giống như hoạt động, mảnh không phải là một Context. Bạn không thể truyền giá trị this (tham chiếu đến đối tượng mảnh) để làm ngữ cảnh của trình quản lý bố cục. Tuy nhiên, các mảnh cung cấp một thuộc tính context mà bạn cũng có thể chuyển sang sử dụng. Phần còn lại của mã sẽ giống hệt với MainActivity.
private fun chooseLayout() {
   when (isLinearLayoutManager) {
       true -> {
           recyclerView.layoutManager = LinearLayoutManager(context)
           recyclerView.adapter = LetterAdapter()
       }
       false -> {
           recyclerView.layoutManager = GridLayoutManager(context, 4)
           recyclerView.adapter = LetterAdapter()
       }
   }
}

private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_linear_layout)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           isLinearLayoutManager = !isLinearLayoutManager
           chooseLayout()
           setIcon(item)

           return true
       }

       else -> super.onOptionsItemSelected(item)
   }
}
  1. Cuối cùng, hãy sao chép thuộc tính isLinearLayoutManager từ MainActivity. Đặt phần này ngay bên dưới phần khai báo thuộc tính recyclerView.
private var isLinearLayoutManager = true
  1. Bây giờ, tất cả chức năng đã được chuyển vào LetterListFragment, tất cả những gì lớp MainActivity cần làm là tăng cường bố cục để mảnh xuất hiện trong thành phần hiển thị. Hãy tiếp tục và xoá mọi thứ trong MainActivity, ngoại trừ onCreate(). Sau khi thay đổi, MainActivity chỉ chứa nội dung sau.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

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

Đến lượt bạn

Đó là những gì cần làm để di chuyển MainActivity sang LettersListFragment. Việc di chuyển DetailActivity gần như giống hệt. Thực hiện các bước sau để di chuyển mã sang WordListFragment.

  1. Sao chép đối tượng đồng hành từ DetailActivity sang WordListFragment. Đảm bảo tham chiếu đến SEARCH_PREFIX trong WordAdapter được cập nhật thành tham chiếu WordListFragment.
  2. Thêm biến _binding. Biến này phải rỗng và có giá trị ban đầu là null.
  3. Thêm biến chỉ nhận có tên là binding được gán bằng biến _binding.
  4. Tăng cường bố cục trong onCreateView(), thiết lập giá trị của _binding và trả về thành phần hiển thị gốc.
  5. Thực hiện các bước thiết lập còn lại trong onViewCreated(): tham chiếu đến thành phần hiển thị tái sinh, thiết lập trình quản lý và chuyển đổi bố cục, đồng thời bổ sung phần trang trí cho thành phần hiển thị này. Bạn cần lấy được chữ cái trả về qua ý định. Vì các mảnh không có thuộc tính intent và thường không được truy cập vào ý định của hoạt động mẹ. Hiện tại, bạn hãy tham chiếu activity.intent (thay vì intent trong DetailActivity) để sử dụng các tính năng bổ sung.
  6. Đặt lại giá trị _binding bằng rỗng (null) trong onDestroyView.
  7. Xoá phần mã còn lại khỏi DetailActivity, chỉ để lại phương thức onCreate().

Bạn hãy thử tự thực hiện các bước trước khi tiếp tục. Hướng dẫn chi tiết sẽ được trình bày ở bước tiếp theo.

6. Chuyển đổi DetailActivity thành WordListFragment

Hy vọng bạn cảm thấy thích thú với việc di chuyển DetailActivity sang WordListFragment. Quá trình này gần giống với việc di chuyển MainActivity sang LetterListFragment. Nếu bạn gặp vướng mắc ở bất cứ điểm nào, hãy tham khảo các bước tóm tắt dưới đây.

  1. Trước tiên, hãy sao chép đối tượng đồng hành vào WordListFragment.
companion object {
   val LETTER = "letter"
   val SEARCH_PREFIX = "https://www.google.com/search?q="
}
  1. Sau đó, trong LetterAdapter, trong phương thức onClickListener() dùng để thực hiện ý định, bạn cần cập nhật lời gọi hàm putExtra(), thay thế DetailActivity.LETTER bằng WordListFragment.LETTER.
intent.putExtra(WordListFragment.LETTER, holder.button.text.toString())
  1. Tương tự, trong WordAdapter, bạn cần cập nhật onClickListener() dùng để chuyển kết quả của từ tìm kiếm, thay thế DetailActivity.SEARCH_PREFIX bằng WordListFragment.SEARCH_PREFIX.
val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}")
  1. Trở lại WordListFragment, bạn sẽ thêm một biến liên kết thuộc kiểu FragmentWordListBinding?.
private var _binding: FragmentWordListBinding? = null
  1. Sau đó, bạn sẽ tạo biến chỉ nhận để tham chiếu các thành phần hiển thị mà không cần sử dụng ?.
private val binding get() = _binding!!
  1. Sau đó, bạn sẽ tăng cường bố cục, gán biến _binding và trả về thành phần hiển thị gốc. Hãy nhớ rằng đối với các mảnh, bạn thực hiện việc này trong onCreateView(), không phải trong onCreate().
override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentWordListBinding.inflate(inflater, container, false)
   return binding.root
}
  1. Tiếp theo là triển khai onViewCreated(). Bước này gần giống với việc định cấu hình recyclerView trong onCreate() trong lớp DetailActivity. Tuy nhiên, vì các mảnh không có quyền truy cập trực tiếp vào intent, bạn cần tham chiếu ý định thông qua activity.intent. Dù vậy bạn phải thực hiện việc này trong onViewCreated(), vì không có gì đảm bảo rằng hoạt động sẽ xảy ra sớm hơn trong vòng đời.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   val recyclerView = binding.recyclerView
   recyclerView.layoutManager = LinearLayoutManager(requireContext())
   recyclerView.adapter = WordAdapter(activity?.intent?.extras?.getString(LETTER).toString(), requireContext())

   recyclerView.addItemDecoration(
       DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
   )
}
  1. Cuối cùng, bạn có thể đặt lại giá trị biến _binding trong onDestroyView().
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. Sau khi tất cả chức năng trên được chuyển vào WordListFragment, bạn có thể xoá phần mã còn lại khỏi DetailActivity, chỉ để lại phương thức onCreate().
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityDetailBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

Xoá DetailActivity

Bạn đã di chuyển thành công chức năng của DetailActivity sang WordListFragment và không cần dùng đến DetailActivity nữa. Bạn có thể tiếp tục và xoá cả DetailActivity.ktactivity_detail.xml cũng như thực hiện một thay đổi nhỏ đối với tệp kê khai.

  1. Trước hết, hãy xoá DetailActivity.kt

2b13b08ac9442ae5.png

  1. Hãy nhớ bỏ đánh dấu tuỳ chọn Xoá an toàn rồi nhấp vào OK.

239f048d945ab1f9.png

  1. Tiếp theo, hãy xoá activity_detail.xml. Hãy nhớ bỏ đánh dấu tuỳ chọn Xoá an toàn một lần nữa.

774c8b152c5bff6b.png

  1. Cuối cùng, do DetailActivity không còn tồn tại nữa, hãy xoá nội dung sau khỏi AndroidManifest.xml.
<activity
   android:name=".DetailActivity"
   android:parentActivityName=".MainActivity" />

Sau khi xoá activity_detail.xml, sẽ còn lại 2 mảnh (ListFragment và WordListFragment) và 1 hoạt động (MainActivity). Trong phần tiếp theo, bạn sẽ tìm hiểu về thành phần điều hướng của Jetpack (Jetpack Navigation component) và chỉnh sửa activity_main.xml để hiển thị và di chuyển giữa các mảnh, thay vì lưu trữ một bố cục tĩnh.

7. Thành phần điều hướng Jetpack

Android Jetpack cung cấp Thành phần điều hướng (Navigation component) để giúp bạn xử lý việc triển khai quá trình di chuyển trong ứng dụng, dù đơn giản hay phức tạp. Thành phần điều hướng có ba phần chính được dùng để triển khai việc di chuyển trong ứng dụng Words.

  • Biểu đồ điều hướng: Biểu đồ điều hướng là tệp XML cung cấp hình biểu diễn điều hướng trực quan trong ứng dụng. Tệp này bao gồm các đích đến tương ứng với từng hoạt động và mảnh riêng lẻ cũng như thao tác giữa các hoạt động và mảnh có thể sử dụng trong mã để di chuyển từ đích đến này tới đích đến khác. Giống như tệp bố cục, Android Studio cung cấp trình chỉnh sửa trực quan để thêm các đích đến và thao tác vào biểu đồ điều hướng.
  • NavHost: NavHost dùng để hiển thị các đích đến từ biểu đồ điều hướng bên trong một hoạt động. Khi bạn di chuyển giữa các mảnh, đích đến hiện ra trong NavHost sẽ được cập nhật. Bạn sẽ dùng phương thức triển khai được tích hợp sẵn có tên NavHostFragment trong MainActivity.
  • NavController: Đối tượng NavController cho phép kiểm soát việc di chuyển giữa các đích đến hiển thị trong NavHost. Khi xử lý các ý định, bạn phải gọi phương thức startActivity để chuyển đến một màn hình mới. Với thành phần điều hướng, bạn có thể gọi phương thức navigate() của NavController để hoán đổi mảnh sẽ xuất hiện. NavController cũng giúp bạn xử lý các thao tác thông thường như phản hồi nút "up" ("trước") của hệ thống để quay lại mảnh hiển thị trước đó.
  1. Trong tệp build.gradle ở cấp dự án, trong buildscript > ext, bên dưới material_version, hãy đặt nav_version bằng 2.5.2.
buildscript {
    ext {
        appcompat_version = "1.5.1"
        constraintlayout_version = "2.1.4"
        core_ktx_version = "1.9.0"
        kotlin_version = "1.7.10"
        material_version = "1.7.0-alpha2"
        nav_version = "2.5.2"
    }

    ...
}
  1. Trong tệp build.gradle ở cấp ứng dụng, hãy thêm nội dung sau đây vào nhóm phần phụ thuộc:
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Trình bổ trợ Safe Args

Trong lần đầu triển khai tính năng điều hướng trong ứng dụng Words, bạn đã sử dụng một ý định tường minh giữa hai hoạt động. Để truyền dữ liệu giữa hai hoạt động này, bạn đã gọi phương thức putExtra(), truyền vào chữ cái đã chọn.

Trước khi bắt đầu triển khai thành phần điều hướng trong ứng dụng Words, bạn cũng sẽ thêm một nội dung là Safe Args – một trình bổ trợ của Gradle, hỗ trợ tính năng nhập an toàn (type safety) khi truyền dữ liệu giữa các mảnh.

Thực hiện các bước sau để tích hợp SafeArgs vào dự án.

  1. Trong tệp build.gradle ở cấp cao nhất, trong buildscript > dependencies, hãy thêm biến môi trường classpath sau đây.
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
  1. Trong tệp build.gradle ở cấp ứng dụng, bên trong plugins ở trên cùng, hãy thêm androidx.navigation.safeargs.kotlin.
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs.kotlin'
}
  1. Sau khi chỉnh sửa các tệp Gradle, bạn sẽ thấy một biểu ngữ màu vàng ở trên cùng đề nghị bạn đồng bộ hoá dự án. Nhấp vào "Sync Now" ("Đồng bộ hoá ngay") rồi đợi một hoặc hai phút trong khi Gradle cập nhật các phần phụ thuộc của dự án để phản ánh các thay đổi của bạn.

854d44a6f7c4c080.png

Sau khi đồng bộ hoá xong, bạn đã sẵn sàng để bắt đầu thêm biểu đồ điều hướng.

8. Sử dụng Biểu đồ điều hướng

Khi đã nắm được các yếu tố cơ bản về mảnh và vòng đời của mảnh, đã đến lúc bạn có thể khám phá nhiều điều thú vị hơn. Bước tiếp theo là kết hợp thành phần điều hướng. Thành phần điều hướng đơn giản chỉ là tập hợp công cụ để triển khai quá trình di chuyển, đặc biệt là di chuyển giữa các mảnh. Bạn sẽ làm việc với một trình chỉnh sửa trực quan mới có tên Biểu đồ điều hướng (Navigation Graph hoặc viết tắt là NavGraph), để triển khai việc di chuyển giữa các mảnh.

Biểu đồ điều hướng là gì?

Biểu đồ điều hướng (viết tắt là NavGraph) là một liên kết ảo, hỗ trợ quá trình điều hướng trong ứng dụng. Mỗi màn hình hoặc mảnh trong trường hợp của bạn đều có thể trở thành một "đích đến" trong quá trình điều hướng. NavGraph có thể được biểu diễn bằng một tệp XML biểu hiện mối liên hệ giữa các đích đến với nhau.

Trên thực tế, thao tác này thực sự sẽ tạo ra một thực thể mới của lớp NavGraph. Tuy nhiên, FragmentContainerView sẽ cho người dùng thấy các đích đến trong biểu đồ điều hướng. Bạn chỉ cần tạo một tệp XML và định nghĩa những đích đến có thể xảy ra. Sau đó, bạn có thể sử dụng mã được tạo để di chuyển giữa các mảnh.

Sử dụng FragmentContainerView trong MainActivity

Hiện tại, do bố cục của bạn đã nằm trong fragment_letter_list.xmlfragment_word_list.xml, tệp activity_main.xml của bạn không cần chứa bố cục cho màn hình đầu tiên trong ứng dụng nữa. Thay vào đó, bạn sẽ sử dụng lại MainActivity để chứa FragmentContainerView, nhằm đóng vai trò là NavHost cho các mảnh trong ứng dụng. Từ thời điểm này trở đi, tất cả thao tác trong ứng dụng sẽ diễn ra trong FragmentContainerView.

  1. Thay thế nội dung của FrameLayout trong activity_main.xml, trong đó androidx.recyclerview.widget.RecyclerView sẽ thay bằng FragmentContainerView. Gán mã nhận dạng cho thành phần hiển thị này bằng mã nhận dạng của nav_host_fragment, đồng thời đặt chiều cao và chiều rộng thành match_parent để lấp đầy toàn bộ bố cục khung.

Thay thế đoạn mã này:

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        ...
        android:padding="16dp" />

bằng:

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/nav_host_fragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Bên dưới thuộc tính mã nhận dạng (id), hãy thêm thuộc tính name rồi đặt thành androidx.navigation.fragment.NavHostFragment. Mặc dù có thể chỉ định một mảnh cụ thể cho thuộc tính này, nhưng việc đặt thuộc tính này thành NavHostFragment sẽ cho phép FragmentContainerView di chuyển giữa các đoạn.
android:name="androidx.navigation.fragment.NavHostFragment"
  1. Bên dưới các thuộc tính layout_height và layout_width, hãy thêm thuộc tính có tên app:defaultNavHost và đặt giá trị bằng "true". Thao tác này cho phép vùng chứa mảnh tương tác với hệ thống phân cấp điều hướng. Ví dụ: nếu bạn nhấn nút quay lại trên hệ thống thì vùng chứa sẽ chuyển về mảnh xuất hiện trước đó, tương tự như những gì sẽ xảy ra khi một hoạt động mới được xuất hiện.
app:defaultNavHost="true"
  1. Thêm một thuộc tính có tên app:navGraph rồi đặt thuộc tính đó bằng "@navigation/nav_graph". Thao tác này sẽ trỏ đến một tệp XML giúp định nghĩa cách thức các mảnh di chuyển trong ứng dụng. Hiện tại, Android Studio sẽ hiện một lỗi biểu tượng chưa được giải quyết. Bạn sẽ giải quyết vấn đề này trong nhiệm vụ tiếp theo.
app:navGraph="@navigation/nav_graph"
  1. Cuối cùng, vì bạn đã thêm hai thuộc tính vào không gian tên của ứng dụng, hãy nhớ thêm thuộc tính xmlns:app vào FrameLayout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

Trên đây là tất cả những gì cần thay đổi trong activity_main.xml. Tiếp theo, bạn sẽ tạo tệp nav_graph.

Thiết lập biểu đồ điều hướng

Thêm một tệp biểu đồ điều hướng (Tệp > Mới > Tệp tài nguyên Android) rồi điền thông tin các trường như sau.

  • File Name (Tên tệp): nav_graph.xml., giống với tên bạn đặt cho thuộc tính app:navGraph.
  • Resource Type (Kiểu tài nguyên): Navigation (Điều hướng). Thông tin trong trường Directory Name (Tên thư mục) sẽ tự động chuyển thành navigation. Hệ thống sẽ tạo một thư mục tài nguyên mới có tên "navigation".

6812c83aa1e9cea6.png

Sau khi tạo tệp XML, bạn sẽ thấy một trình chỉnh sửa trực quan mới. Vì bạn đã tham chiếu nav_graph trong thuộc tính navGraph của FragmentContainerView, nên để thêm một đích đến mới, hãy nhấp vào nút lệnh mới ở trên cùng bên trái màn hình rồi tạo đích đến cho từng mảnh (một đích đến cho fragment_letter_list và một cho fragment_word_list).

dc2b53782de5e143.gif

Sau khi thêm, các mảnh này sẽ xuất hiện trên biểu đồ điều hướng ở giữa màn hình. Bạn cũng có thể chọn một đích đến cụ thể bằng cách sử dụng cây thành phần ở bên trái.

Tạo một thao tác điều hướng

Để tạo một thao tác điều hướng giữa các đích đến letterListFragmentwordListFragment, hãy di chuột qua đích đến letterListFragment rồi kéo từ vòng tròn xuất hiện ở bên phải tới đích đến wordListFragment.

980cb34d800c7155.gif

Bây giờ, bạn sẽ thấy một mũi tên được tạo ra để thể hiện thao tác giữa hai đích đến này. Nhấp vào mũi tên, ở trong ngăn thuộc tính bạn sẽ thấy rằng thao tác này có tên action_letterListFragment_to_wordListFragment có thể tham chiếu trong mã.

Chỉ định đối số cho WordListFragment

Khi di chuyển giữa các hoạt động với một ý định nào đó, bạn đã chỉ định "extra" để truyền chữ cái đã chọn đến wordListFragment. Tính năng điều hướng cũng hỗ trợ truyền các tham số giữa các đích đến một cách an toàn.

Chọn đích đến wordListFragment và trong ngăn thuộc tính, dưới phần Arguments (Đối số), hãy nhấp vào nút dấu cộng để tạo một đối số mới.

Đặt tên cho đối số là letter, thuộc kiểu String. Đây cũng chính là nơi bạn từng thêm trình bổ trợ Safe Args. Bằng việc chỉ định đối số này dưới dạng chuỗi, bạn có thể đảm bảo String đã được liệu trước khi thao tác điều hướng của bạn được thực hiện trong mã.

f1541e01d3462f3e.png

Thiết lập đích đến bắt đầu

Mặc dù NavGraph nhận biết tất cả đích đến cần thiết, nhưng làm sao FragmentContainerView biết được cần hiện mảnh nào đầu tiên? Trên NavGraph, bạn cần thiết lập danh sách chữ cái dùng làm đích đến bắt đầu.

Thiết lập đích đến bắt đầu bằng cách chọn letterListFragment rồi nhấp vào nút Assign start destination (Gán đích đến bắt đầu).

3fdb226894152fb0.png

  1. Hiện tại, đó là tất cả những gì bạn cần làm trên trình chỉnh sửa NavGraph. Bây giờ, hãy tiếp tục và xây dựng dự án. Trong Android Studio, hãy chọn Build (Xây dựng) > Rebuild Project (Xây dựng lại dự án) trên thanh trình đơn. Thao tác này sẽ tạo ra một số mã dựa trên biểu đồ điều hướng, cho phép bạn sử dụng thao tác điều hướng vừa tạo.

Thực hiện thao tác điều hướng

Mở LetterAdapter.kt để thực hiện thao tác điều hướng. Quá trình này chỉ cần đến hai bước.

  1. Xoá nội dung trong setOnClickListener() của nút (button). Thay vào đó, bạn cần truy xuất thao tác điều hướng vừa tạo. Thêm nội dung dưới đây vào setOnClickListener().
val action = LetterListFragmentDirections.actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString())

Có thể bạn không nhận ra một số tên hàm và lớp trong số này. Lý do là những tên hàm và lớp này được tạo tự động sau khi bạn xây dựng dự án. Đó là nơi bạn thêm trình bổ trợ Safe Args trong bước đầu tiên. Các thao tác được tạo trên Nav Graph sẽ được chuyển thành mã mà bạn có thể sử dụng. Tuy nhiên, những tên hàm và lớp này khá dễ hiểu. LetterListFragmentDirections biểu hiện tất cả đường dẫn điều hướng có thể xuất phát từ letterListFragment.

Hàm actionLetterListFragmentToWordListFragment()

biểu hiện một thao tác cụ thể để chuyển đến wordListFragment.

Sau khi tham chiếu đến thao tác điều hướng, bạn chỉ cần tham chiếu đến NavController (đối tượng giúp bạn thực hiện thao tác điều hướng) rồi gọi navigate() truyền qua thao tác đó.

holder.view.findNavController().navigate(action)

Định cấu hình MainActivity

Bước thiết lập cuối cùng là cho MainActivity. Chỉ cần thực hiện một vài thay đổi nữa trong MainActivity là mọi thứ bắt đầu hoạt động được.

  1. Tạo thuộc tính navController. Thuộc tính này sẽ được thiết lập trong onCreate nên sẽ được đánh dấu là lateinit.
private lateinit var navController: NavController
  1. Tiếp đến, sau khi gọi hàm setContentView() trong onCreate(), hãy lấy thông tin tham chiếu đến nav_host_fragment (đây là mã nhận dạng của FragmentContainerView) rồi gán thông tin này cho thuộc tính navController.
val navHostFragment = supportFragmentManager
    .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
  1. Sau đó, trong onCreate(), hãy gọi setupActionBarWithNavController(), truyền vào navController. Bước này giúp đảm bảo các nút xuất hiện được trên thanh thao tác (thanh ứng dụng), chẳng hạn như tuỳ chọn trình đơn trong LetterListFragment.
setupActionBarWithNavController(navController)
  1. Cuối cùng, hãy triển khai onSupportNavigateUp(). Ngoài việc thiết lập defaultNavHost bằng true bằng XML, phương thức này cho phép bạn xử lý nút up (trước). Tuy nhiên, hoạt động của bạn cần đưa ra việc triển khai.
override fun onSupportNavigateUp(): Boolean {
   return navController.navigateUp() || super.onSupportNavigateUp()
}

Bây giờ, tất cả thành phần đều sẵn sàng để chức năng điều hướng hoạt động được giữa các mảnh. Tuy nhiên, việc điều hướng giờ đây được thực hiện thông qua các mảnh thay vì ý định. Ý định bổ sung cho chữ cái sử dụng trong WordListFragment sẽ không hoạt động nữa. Trong bước tiếp theo, bạn sẽ cập nhật WordListFragment để lấy đối số letter.

9. Lấy đối số trong WordListFragment

Trước đây, bạn từng tham chiếu đến activity?.intent trong WordListFragment để truy cập vào letter bổ sung (extra). Mặc dù cách này mang lại hiệu quả, nhưng đây không phải là phương pháp hay nhất vì các mảnh có thể được nhúng vào các bố cục khác và trong một ứng dụng lớn hơn. Do đó, sẽ rất khó để giả định một mảnh thuộc về hoạt động nào. Hơn nữa, nếu thực hiện việc điều hướng bằng nav_graph và sử dụng các đối số an toàn, thì sẽ không tồn tại bất cứ ý định nào. Do đó, việc cố gắng truy cập vào các tính năng bổ sung của ý định sẽ không hiệu quả.

May mắn là khá đơn giản để truy cập vào các đối số an toàn và bạn cũng không phải đợi đến khi onViewCreated() được gọi.

  1. Trong WordListFragment, hãy tạo một thuộc tính letterId. Bạn có thể đánh dấu lateinit để thuộc tính này không chứa giá trị rỗng.
private lateinit var letterId: String
  1. Sau đó, hãy ghi đè onCreate() (không phải onCreateView() hoặc onViewCreated()!) rồi thêm đoạn mã sau:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    arguments?.let {
        letterId = it.getString(LETTER).toString()
    }
}

arguments có thể không bắt buộc nên bạn cần gọi phương thức let() và truyền vào giá trị lambda. Mã này sẽ thực thi với giả định arguments khác rỗng, truyền các đối số khác rỗng cho tham số it. Tuy nhiên, nếu argumentsnull, thì lambda sẽ không thực thi.

96a6a3253cea35b0.png

Mặc dù đây không phải là một phần mã thực tế, Android Studio cung cấp cho bạn một gợi ý hữu ích để hiểu thêm về tham số it.

Vậy chính xác thì Bundle là gì? Hãy xem đây là một cặp khoá-giá trị được dùng để truyền dữ liệu giữa các lớp, chẳng hạn như hoạt động và mảnh. Bạn đã sử dụng gói (bundle) khi gọi intent?.extras?.getString() để thực hiện một ý định trong phiên bản đầu tiên của ứng dụng này. Cách lấy chuỗi từ đối số khi xử lý các mảnh cũng thực hiện tương tự.

  1. Cuối cùng, bạn có thể truy cập letterId khi thiết lập bộ chuyển đổi của thành phần hiển thị tái sinh. Thay thế activity?.intent?.extras?.getString(LETTER).toString() trong onViewCreated() bằng letterId.
recyclerView.adapter = WordAdapter(letterId, requireContext())

Bạn đã làm được! Hãy dành một chút thời gian để chạy ứng dụng của bạn. Giờ đây, bạn có thể di chuyển giữa hai màn hình mà không cần ý định nào và tất cả đều thực hiện trong một hoạt động duy nhất.

10. Cập nhật nhãn mảnh

Bạn đã chuyển đổi thành công cả hai màn hình để sử dụng các mảnh. Trước khi thực hiện thay đổi nào, thanh ứng dụng cho từng mảnh sẽ có tiêu đề mô tả cho mỗi hoạt động có trong thanh ứng dụng. Tuy nhiên, sau khi chuyển đổi để sử dụng các mảnh, tiêu đề này đang bị thiếu trong phần hoạt động chi tiết.

c385595994ba91b5.png

Các mảnh có một thuộc tính tên là "label", trong đó bạn có thể đặt tiêu đề để hoạt động mẹ sử dụng trong thanh ứng dụng.

  1. Trong strings.xml, sau tên ứng dụng, hãy thêm hằng số sau.
<string name="word_list_fragment_label">Words That Start With {letter}</string>
  1. Bạn có thể đặt nhãn cho từng mảnh trên biểu đồ điều hướng. Quay lại với nav_graph.xml, chọn letterListFragment trong cây thành phần, sau đó ở ngăn thuộc tính, đặt nhãn này thành chuỗi app_name:

a5ffe7a27aa03750.png

  1. Chọn wordListFragment rồi đặt nhãn này thành word_list_fragment_label:

29c206f03a97557b.png

Chúc mừng bạn đã đến được đây! Hãy chạy ứng dụng lần nữa và bạn sẽ thấy mọi thứ như khi bắt đầu lớp học lập trình này. Tuy nhiên, toàn bộ phần điều hướng của bạn giờ đây sẽ được lưu trữ trong một hoạt động duy nhất với một mảnh riêng biệt cho mỗi màn hình.

11. Mã giải pháp

Mã giải pháp 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.

12. Tóm tắt

  • Mảnh là những phần giao diện người dùng có thể sử dụng lại và có thể được nhúng vào các hoạt động.
  • Vòng đời của một mảnh khác với vòng đời của một hoạt động, việc thiết lập thành phần hiển thị được thực hiện trong onViewCreated(), thay vì onCreateView().
  • FragmentContainerView dùng để nhúng các mảnh vào hoạt động khác và có thể quản lý việc di chuyển giữa các mảnh.

Sử dụng thành phần Điều hướng

  • Việc thiết lập thuộc tính navGraph của FragmentContainerView cho phép bạn di chuyển giữa các mảnh trong một hoạt động.
  • Trình chỉnh sửa NavGraph cho phép bạn thêm các thao tác điều hướng và chỉ định đối số giữa nhiều đích đến.
  • Trong khi phương thức điều hướng sử dụng ý định yêu cầu bạn truyền tham số trong các lớp bổ sung, thành phần Điều hướng sử dụng SafeArgs để tự động tạo các lớp và phương thức cho các thao tác điều hướng, đảm bảo an toàn về kiểu cho các đối số.

Trường hợp sử dụng mảnh

  • Bằng cách sử dụng thành phần Điều hướng, nhiều ứng dụng có thể quản lý toàn bộ bố cục trong một hoạt động duy nhất, trong đó toàn bộ quá trình điều hướng diễn ra giữa các mảnh.
  • Các mảnh có thể tạo ra các mẫu bố cục phổ biến, chẳng hạn như bố cục chi tiết/tổng thể trên máy tính bảng hoặc nhiều thẻ trong cùng một hoạt động.

13. Tìm hiểu thêm