FragmentManager
là lớp chịu trách nhiệm thực hiện hành động trên các mảnh của ứng dụng, ví dụ: thêm, xoá hoặc thay thế mảnh, cũng như thêm mảnh vào ngăn xếp lui.
Bạn không thể tương tác trực tiếp với FragmentManager
nếu đang sử dụng thư viện Điều hướng của Jetpack, vì thư viện này hoạt động với FragmentManager
thay mặt bạn. Tuy nhiên, bất cứ ứng dụng nào sử dụng mảnh cũng sẽ sử dụng FragmentManager
ở mức độ nào đó. Bởi vậy, bạn cần hiểu được khái niệm và cách thức hoạt động của trình quản lý mảnh.
Trang này bao gồm:
- Cách truy cập
FragmentManager
. - Vai trò của
FragmentManager
trong các hoạt động và mảnh. - Cách quản lý ngăn xếp lui bằng
FragmentManager
. - Cách cung cấp dữ liệu và phần phụ thuộc cho các mảnh.
Truy cập FragmentManager
Bạn có thể truy cập FragmentManager
từ một hoạt động hoặc từ một mảnh.
FragmentActivity
và các lớp con của nó, chẳng hạn như AppCompatActivity
, có quyền truy cập vào FragmentManager
thông qua phương thức getSupportFragmentManager()
.
Các mảnh có thể lưu trữ một hoặc nhiều mảnh con. Bên trong một mảnh, bạn có thể lấy thông tin tham chiếu đến FragmentManager
quản lý các mảnh con của mảnh thông qua getChildFragmentManager()
.
Nếu cần truy cập vào FragmentManager
của máy chủ, bạn có thể sử dụng getParentFragmentManager()
.
Dưới đây là một vài ví dụ để thấy mối quan hệ giữa các mảnh, máy chủ lưu trữ và các thực thể FragmentManager
được liên kết với nhau.
![hai ví dụ về bố cục của giao diện người dùng cho thấy mối quan hệ giữa mảnh và hoạt động của máy chủ.](https://developer.android.com/static/images/guide/fragments/fragment-host.png?authuser=6&hl=vi)
Hình 1 cho thấy hai ví dụ, mỗi ví dụ có một máy chủ hoạt động duy nhất. Hoạt động của máy chủ trong cả hai ví dụ này hiển thị thành phần điều hướng cấp cao nhất cho người dùng dưới dạng BottomNavigationView
chịu trách nhiệm hoán đổi mảnh máy chủ có nhiều màn hình trong ứng dụng. Mỗi màn hình được triển khai dưới dạng một mảnh riêng.
Mảnh máy chủ trong Ví dụ 1 lưu trữ hai mảnh con tạo thành một màn hình xem phân tách. Mảnh máy chủ trong Ví dụ 2 lưu trữ một mảnh con duy nhất tạo thành mảnh hiển thị của khung hiển thị vuốt.
Với chế độ thiết lập này, bạn có thể coi mỗi máy chủ được liên kết với một FragmentManager
có chức năng quản lý các mảnh con. Điều này được minh hoạ trong hình 2, cùng với các liên kết thuộc tính giữa supportFragmentManager
, parentFragmentManager
và childFragmentManager
.
![mỗi máy chủ đều được liên kết với FragmentManager riêng có chức năng quản lý các mảnh con](https://developer.android.com/static/images/guide/fragments/manager-mappings.png?authuser=6&hl=vi)
FragmentManager
riêng có chức năng quản lý
các mảnh con.Thuộc tính FragmentManager
thích hợp để tham chiếu phụ thuộc vào
vị trí gọi trong hệ thống phân cấp mảnh, cũng như trình quản lý mảnh
mà bạn đang tìm cách truy cập.
Sau khi có một tham chiếu đến FragmentManager
, bạn có thể sử dụng tham chiếu đó
để điều khiển các mảnh hiển thị cho người dùng.
Mảnh con
Nói chung, ứng dụng của bạn nên bao gồm một hoặc một số ít hoạt động trong dự án ứng dụng, với mỗi hoạt động đại diện cho một nhóm các màn hình liên quan. Hoạt động có thể cung cấp một điểm để đặt thành phần điều hướng ở cấp cao nhất và một vị trí để thu hẹp phạm vi của ViewModel
cũng như trạng thái xem khác giữa các mảnh. Mảnh biểu thị cho một đích đến riêng lẻ trong ứng dụng.
Nếu muốn hiển thị nhiều mảnh cùng một lúc, chẳng hạn như trong thành phần hiển thị phân tách hoặc trang tổng quan, bạn cần sử dụng các mảnh con được mảnh đích quản lý và trình quản lý mảnh con.
Sau đây là các trường hợp sử dụng khác cho mảnh con:
- Chuyển màn hình,
bằng
ViewPager2
trong một mảnh mẹ để quản lý một loạt khung hiển thị mảnh con. - Điều hướng phụ trong một nhóm các màn hình liên quan.
- Thư viện Điều hướng của Jetpack sử dụng các mảnh con làm đích đến riêng lẻ. Một hoạt động lưu trữ một
NavHostFragment
mẹ và lấp đầy không gian bằng nhiều mảnh đích đến con khi người dùng di chuyển trong ứng dụng của bạn.
Sử dụng FragmentManager
FragmentManager
quản lý ngăn xếp lui của mảnh. Trong thời gian chạy, FragmentManager
có thể thực hiện các thao tác ngăn xếp lui như thêm hoặc xoá các mảnh để phản hồi tương tác của người dùng. Mỗi tập hợp thay đổi được xác nhận cùng nhau dưới dạng một đơn vị duy nhất gọi là FragmentTransaction
.
Để thảo luận sâu hơn về giao dịch mảnh, hãy xem hướng dẫn giao dịch mảnh.
Khi người dùng nhấn vào nút "Quay lại" trên thiết bị hoặc khi bạn gọi FragmentManager.popBackStack()
, giao dịch mảnh ở trên cùng sẽ bật ra khỏi ngăn xếp. Nếu không có thêm giao dịch mảnh nào trong ngăn xếp và nếu bạn không sử dụng các mảnh con, thì sự kiện quay lại sẽ xuất hiện trong hoạt động. Nếu bạn đang sử dụng các mảnh con, hãy xem bài viết những điểm cần đặc biệt cân nhắc đối với mảnh con và mảnh đồng cấp.
Khi bạn gọi addToBackStack()
trên một giao dịch, giao dịch đó có thể bao gồm nhiều hoạt động, chẳng hạn như thêm nhiều mảnh hoặc thay thế các mảnh trong nhiều vùng chứa.
Khi ngăn xếp lui được đẩy, tất cả các thao tác này sẽ đảo ngược thành một hành động ở cấp nguyên tử. Tuy nhiên, nếu bạn đã thực hiện các giao dịch bổ sung trước lệnh gọi popBackStack()
và nếu bạn không sử dụng addToBackStack()
cho giao dịch này, thì các thao tác này sẽ không đảo ngược. Do đó, trong một FragmentTransaction
, hãy tránh xen kẽ các giao dịch ảnh hưởng đến ngăn xếp lui với những giao dịch không ảnh hưởng đến ngăn xếp lui.
Thực hiện giao dịch
Để hiển thị một mảnh trong vùng chứa bố cục, hãy sử dụng FragmentManager
để tạo một FragmentTransaction
. Sau đó, trong giao dịch, bạn có thể
thực hiện thao tác
add()
hoặc replace()
trên vùng chứa.
Ví dụ: một mã FragmentTransaction
đơn giản có thể trông như sau:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
Trong ví dụ này, ExampleFragment
sẽ thay thế mảnh (nếu có) hiện ở trong vùng chứa bố cục được xác định bằng mã R.id.fragment_container
. Việc cung cấp lớp của mảnh cho phương thức replace()
cho phép FragmentManager
xử lý việc tạo thực thể bằng cách sử dụng FragmentFactory
.
Để biết thêm thông tin, hãy xem phần Cung cấp các phần phụ thuộc cho mảnh.
setReorderingAllowed(true)
tối ưu hoá các thay đổi về trạng thái của các mảnh có liên quan trong giao dịch để các ảnh động và hiệu ứng chuyển tiếp hoạt động chính xác. Để biết thêm thông tin về cách
di chuyển bằng ảnh động và hiệu ứng chuyển tiếp, hãy xem
Giao dịch mảnh và
Di chuyển giữa các mảnh bằng ảnh động.
Việc gọi addToBackStack()
sẽ xác nhận giao dịch vào ngăn xếp lui. Sau đó, người dùng có thể đảo ngược giao dịch và khôi phục mảnh trước đó bằng cách nhấn nút Quay lại. Nếu bạn đã thêm hoặc xoá nhiều mảnh trong một giao dịch, thì tất cả các thao tác đó sẽ bị huỷ khi ngăn xếp lui bị đẩy ra. Tên tuỳ chọn đã cung cấp trong lệnh gọi addToBackStack()
cho phép bạn quay lại giao dịch cụ thể đó bằng popBackStack()
.
Nếu bạn không gọi addToBackStack()
khi thực hiện giao dịch xoá một mảnh, thì mảnh đã xoá sẽ bị huỷ khi xác nhận giao dịch và người dùng sẽ không thể quay lại mảnh đó. Nếu bạn gọi addToBackStack()
khi xoá một mảnh, thì mảnh đó sẽ chỉ STOPPED
và sau đó sẽ được RESUMED
khi người dùng quay lại. Khung hiển thị của mảnh này bị phá huỷ trong trường hợp này. Để biết thêm thông tin, hãy xem bài viết Vòng đời của mảnh.
Tìm một mảnh hiện có
Bạn có thể lấy thông tin tham chiếu đến mảnh hiện tại trong vùng chứa bố cục bằng cách sử dụng findFragmentById()
.
Hãy sử dụng findFragmentById()
để tìm một mảnh theo mã nhận dạng nhất định khi
được tăng cường từ XML hoặc theo mã vùng chứa khi được thêm vào một
FragmentTransaction
. Sau đây là ví dụ:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
Ngoài ra, bạn có thể gán một thẻ duy nhất cho một mảnh và lấy thông tin tham chiếu bằng findFragmentByTag()
.
Bạn có thể gán một thẻ bằng thuộc tính XML android:tag
trên các mảnh được xác định trong bố cục hoặc trong khi thực hiện thao tác add()
hay replace()
trong một FragmentTransaction
.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
Các điểm cần đặc biệt lưu ý đối với mảnh con và mảnh đồng cấp
Chỉ một FragmentManager
có thể kiểm soát ngăn xếp lui của mảnh tại một thời điểm bất kỳ. Nếu ứng dụng của bạn hiển thị nhiều mảnh đồng cấp trên màn hình cùng một lúc hoặc dùng các mảnh con, thì bạn cần chỉ định một FragmentManager
để xử lý hoạt động điều hướng chính trong ứng dụng.
Để xác định mảnh điều hướng chính bên trong một giao dịch mảnh, hãy gọi phương thức setPrimaryNavigationFragment()
trên giao dịch, chuyển vào thực thể của mảnh mà childFragmentManager
của mảnh đó có quyền kiểm soát chính.
Xem cấu trúc điều hướng là một chuỗi các lớp, với hoạt động là lớp ngoài cùng, bọc từng lớp mảnh con bên dưới. Mỗi lớp có một mảnh điều hướng chính duy nhất.
Khi sự kiện Quay lại xuất hiện, lớp bên trong cùng sẽ kiểm soát hành vi di chuyển. Khi lớp trong cùng không còn giao dịch mảnh nào xuất hiện từ đó, quyền kiểm soát quay lại lớp bên ngoài tiếp theo, quy trình này lặp lại cho đến khi bạn truy cập vào hoạt động.
Khi hai hoặc nhiều mảnh hiển thị cùng một lúc, chỉ một trong số đó là mảnh điều hướng chính. Việc đặt một mảnh làm mảnh điều hướng chính sẽ xoá thông tin chỉ định khỏi mảnh trước đó. Trong ví dụ trước, nếu bạn đặt mảnh chi tiết làm mảnh điều hướng chính, thì thông tin chỉ định của mảnh chính sẽ bị xoá.
Hỗ trợ nhiều ngăn xếp lui
Trong một số trường hợp, ứng dụng của bạn có thể cần hỗ trợ nhiều ngăn xếp lui. Một ví dụ phổ biến là trường hợp ứng dụng sử dụng thanh điều hướng ở dưới cùng. FragmentManager
cho phép bạn hỗ trợ nhiều ngăn xếp lui bằng phương thức saveBackStack()
và restoreBackStack()
. Các phương thức này cho phép bạn hoán đổi giữa các ngăn xếp lui bằng cách lưu một ngăn xếp lui và khôi phục một ngăn xếp lui khác.
saveBackStack()
hoạt động tương tự như cách gọi popBackStack()
bằng
tham số name
tuỳ chọn: giao dịch đã chỉ định và tất cả các giao dịch sau giao thức này
trên ngăn xếp sẽ được đẩy ra. Điểm khác biệt là saveBackStack()
lưu trạng thái của tất cả các mảnh trong các giao dịch đã được đẩy ra.
Ví dụ: giả sử trước đây bạn đã thêm một mảnh vào ngăn xếp lui bằng cách xác nhận một FragmentTransaction
bằng addToBackStack()
, như trong ví dụ sau:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
Trong trường hợp đó, bạn có thể lưu giao dịch mảnh này và trạng thái của
ExampleFragment
bằng cách gọi saveBackStack()
:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
Bạn có thể gọi restoreBackStack()
với cùng tham số tên để khôi phục tất cả giao dịch đã được đẩy ra và mọi trạng thái mảnh đã lưu:
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
Cung cấp phần phụ thuộc cho mảnh
Khi thêm một mảnh, bạn có thể tạo bản sao mảnh theo cách thủ công và
thêm bản sao mảnh đó vào FragmentTransaction
.
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
Khi bạn xác nhận giao dịch mảnh, bản sao của mảnh
bạn đã tạo là thực thể được sử dụng. Tuy nhiên, trong quá trình thay đổi cấu hình, hoạt động và tất cả các mảnh của hoạt động sẽ bị huỷ, sau đó được tạo lại bằng các tài nguyên Android thích hợp nhất.
FragmentManager
xử lý toàn bộ việc này cho bạn: nó tạo lại các thực thể của mảnh, đính kèm các thực thể đó vào máy chủ và tạo lại trạng thái ngăn xếp lui.
Theo mặc định, FragmentManager
sử dụng một FragmentFactory
mà khung cung cấp để tạo thực thể mới của mảnh. Nhà máy
mặc định này sử dụng sự phản chiếu để tìm và gọi một hàm khởi tạo không đối số
cho mảnh. Điều này có nghĩa là bạn không thể sử dụng nhà máy mặc định này để
cung cấp các phần phụ thuộc cho mảnh. Điều này cũng có nghĩa là mọi hàm khởi tạo tuỳ chỉnh mà bạn sử dụng để tạo mảnh lần đầu sẽ không được dùng trong quá trình tạo lại theo mặc định.
Để cung cấp các phần phụ thuộc cho mảnh hoặc để sử dụng bất kỳ hàm khởi tạo tuỳ chỉnh nào, bạn phải tạo một lớp con FragmentFactory
tuỳ chỉnh, sau đó ghi đè FragmentFactory.instantiate
.
Sau đó, bạn có thể ghi đè factory mặc định của FragmentManager
bằng factory tuỳ chỉnh của mình. Sau đó, factory này được dùng để tạo thực thể cho các mảnh.
Giả sử bạn có DessertsFragment
chịu trách nhiệm hiển thị các món tráng miệng phổ biến tại quê nhà và DessertsFragment
có phần phụ thuộc vào một lớp DessertsRepository
giúp cung cấp thông tin mà lớp đó cần để hiển thị đúng giao diện người dùng cho người dùng.
Bạn có thể xác định DessertsFragment
để yêu cầu bản sao của DessertsRepository
trong hàm khởi tạo.
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
Cách triển khai FragmentFactory
đơn giản có thể tương tự
như sau.
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
Ví dụ này phân lớp FragmentFactory
, ghi đè phương thức instantiate()
để cung cấp logic tạo mảnh tuỳ chỉnh cho DessertsFragment
.
Các lớp mảnh khác được xử lý bởi hành vi mặc định của
FragmentFactory
thông qua super.instantiate()
.
Sau đó, bạn có thể chỉ định MyFragmentFactory
làm nhà máy để sử dụng khi
tạo các mảnh của ứng dụng bằng cách đặt một thuộc tính trên
FragmentManager
. Bạn phải đặt thuộc tính này trước super.onCreate()
của hoạt động
để đảm bảo rằng MyFragmentFactory
được sử dụng khi
tạo lại các mảnh.
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
Việc đặt FragmentFactory
trong hoạt động sẽ ghi đè hoạt động tạo mảnh trong hệ phân cấp mảnh của hoạt động. Nói cách khác, childFragmentManager
của bất kỳ mảnh con nào mà bạn thêm vào sẽ sử dụng factory mảnh tuỳ chỉnh được đặt ở đây trừ trường hợp bị ghi đè ở cấp thấp hơn.
Kiểm thử với FragmentFactory
Trong một cấu trúc hoạt động đơn lẻ, hãy kiểm thử các mảnh để tách biệt bằng cách sử dụng lớp FragmentScenario
. Vì không thể dựa vào logic onCreate
tuỳ chỉnh của hoạt động, nên bạn có thể chuyển FragmentFactory
dưới dạng một đối số cho phần kiểm thử mảnh của mình, như minh hoạ trong ví dụ sau:
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
Để biết thông tin chi tiết về quy trình kiểm thử này và xem các ví dụ đầy đủ, vui lòng xem bài viết Kiểm thử các mảnh.