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.
Chủ đề này sẽ trình bày về cách truy cập vàoFragmentManager
, vai trò của
FragmentManager
liên quan đến các hoạt động và mảnh, quản lý
ngăn xếp lui bằng FragmentManager
, cũng như cung cấp dữ liệu và phần phụ thuộc
cho các mảnh.
Truy cập FragmentManager
Truy cập trong một hoạt động
Mỗi FragmentActivity
và các lớp con của nó, chẳng hạn như
AppCompatActivity
,
có thể truy cập vàoFragmentManager
thông qua
phương thức
getSupportFragmentManager()
.
Truy cập trong một mảnh
Các mảnh cũng 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()
.
Hãy xem một vài ví dụ để thấy mối quan hệ giữa
các mảnh, các máy chủ của chúng, cũng như các bản sao FragmentManager
liên kết
với mỗi mảnh.

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, trong đó 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 thành phần 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
.

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 ViewModels
cũng như trạng thái xem khác
giữa các mảnh. Mỗi đích đến riêng lẻ trong ứng dụng phải
được biểu thị bằng một mảnh.
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.
Các trường hợp sử dụng khác cho mảnh con có thể bao gồm:
- Chuyển màn hình,
bằng
ViewPager2
trong một mảnh mẹ để quản lý một loạt thành phần 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 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ẽ được đẩy khỏi ngăn xếp. Nói cách khác,
giao dịch sẽ đảo ngược. 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
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 điện
addToBackStack()
trên một giao dịch, hãy lưu ý rằng giao dịch đó có thể bao gồm
nhiều hoạt động, chẳng hạn như thêm nhiều mảnh, thay thế mảnh trong nhiều
vùng chứa, v.v. Khi ngăn xếp lui được đẩy, tất cả các thao tác này
sẽ được đảo ngược thành một hành động nguyên tử. Nếu bạn đã xác nhận
các giao dịch bổ sung trước lệnh gọi popBackStack()
, cũng như
không sử dụng addToBackStack()
cho giao dịch, 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()
sẽ cho phép FragmentManager
xử lý quá trình tạo bản sao bằng cách sử dụng
FragmentFactory
.
Để biết thêm thông tin, hãy xem bài viết Cung cấp các phần phụ thuộc.
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 bạn 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. Lưu ý rằng
thành phần hiển thị của mảnh sẽ bị 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ỉ cho phép một FragmentManager
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 phải chỉ định một
FragmentManager
để xử lý đ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 bản sao của mảnh mà
childFragmentManager
của mảnh đó phải 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 phải 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.
Xin lưu ý rằng khi hai hoặc nhiều mảnh hiển thị cùng một lúc, chỉ một trong số các mảnh đó có thể 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á 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ì 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 chuyể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 đó 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()
:
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ý mọi vấn đề này cho bạn. Trình quản lý mảnh sẽ tạo lại các bản sao
của các mảnh, đính kèm các bản sao đó 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 bản sao 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 đè nhà máy mặc định của FragmentManager
bằng
nhà máy tuỳ chỉnh của bạn mà sau đó dùng để tạo bản sao của 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à của bạn. Giả sử rằng DessertsFragment
có một phần phụ thuộc trên lớp DessertsRepository
, phần phụ thuộc này cung cấp cho mảnh
thông tin cần thiết để 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); } }
Xin lưu ý rằng khi đặt FragmentFactory
trong hoạt động, việc tạo mảnh sẽ bị ghi đè
trong toàn bộ 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 nhà máy 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ẻ, bạn nên kiểm thử riêng các mảnh
bằ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ư hiển thị 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, cũng như tham khảo các ví dụ đầy đủ, hãy xem phần Kiểm thử các mảnh của ứng dụng.