lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Phân đoạn

Fragment biểu diễn một hành vi hay một phần giao diện người dùng trong một Activity. Bạn có thể kết hợp nhiều phân đoạn trong một hoạt động duy nhất để xây dựng một UI nhiều bảng và sử dụng lại phân đoạn trong nhiều hoạt động. Bạn có thể coi phân đoạn như là một phần mô-đun của một hoạt động, có vòng đời của chính nó, nhận các sự kiện đầu vào của chính nó, và bạn có thể thêm hoặc gỡ bỏ trong khi hoạt động đang chạy (kiểu như một "hoạt động con" mà bạn có thể sử dụng lại trong các hoạt động khác nhau).

Phân đoạn phải luôn được nhúng trong một hoạt động và vòng đời của phân đoạn bị ảnh hưởng trực tiếp bởi vòng đời của hoạt động chủ. Ví dụ, khi hoạt động bị tạm dừng, tất cả phân đoạn trong nó cũng vậy, và khi hoạt động bị hủy, tất cả phân đoạn cũng vậy. Tuy nhiên, trong khi một hoạt động đang chạy (nó ở trong trạng thái vòng đời được tiếp tục), bạn có thể thao tác từng phân đoạn độc lập, chẳng hạn như thêm hay xóa chúng. Khi bạn thực hiện một giao tác phân đoạn, bạn cũng có thể thêm nó vào một ngăn xếp được quản lý bởi hoạt động đó—từng mục nhập vào ngăn xếp trong hoạt động là một bản ghi giao tác phân đoạn đã xảy ra. Ngăn xếp cho phép người dùng đảo ngược một giao tác phân đoạn (điều hướng ngược lại), bằng cách nhấn nút Quay lại.

Khi bạn thêm một phân đoạn như một phần trong bố trí hoạt động của mình, nó sẽ ở trong một ViewGroup bên trong phân cấp dạng xem của hoạt động đó và phân đoạn này sẽ định nghĩa bố trí dạng xem của chính nó. Bạn có thể chèn một phân đoạn vào bố trí hoạt động của mình bằng cách khai báo phân đoạn trong tệp bố trí của hoạt động, dưới dạng một phần tử <fragment>, hoặc từ mã ứng dụng của bạn bằng cách thêm nó vào một ViewGroup hiện hữu. Tuy nhiên, không bắt buộc phải có một phân đoạn là một bộ phận của bố trí hoạt động ; bạn cũng có thể sử dụng một phân đoạn mà không cần UI của chính nó như một trình thực hiện vô hình cho hoạt động .

Tài liệu này mô tả cách xây dựng ứng dụng của bạn để sử dụng phân đoạn, bao gồm cách các phân đoạn có thể duy trì trạng thái của chúng khi được thêm vào ngăn xếp của hoạt động, chia sẻ các sự kiện với hoạt động và các phân đoạn khác trong hoạt động, đóng góp vào thanh hành động của hoạt động và nhiều thông tin khác.

Triết lý Thiết kế

Android giới thiệu phân đoạn trong phiên bản Android 3.0 (API mức 11), chủ yếu nhằm hỗ trợ các thiết kế UI động và linh hoạt hơn trên màn hình lớn, chẳng hạn như máy tính bảng. Vì màn hình của máy tính bảng lớn hơn nhiều màn hình của thiết bị cầm tay, có nhiều khoảng trống hơn để kết hợp và trao đổi các thành phần UI. Phân đoạn cho phép những thiết kế như vậy mà không cần bạn phải quản lý những thay đổi phức tạp về phân cấp dạng xem. Bằng cách chia bố trí của một hoạt động thành các phân đoạn, bạn có thể sửa đổi diện mạo của hoạt động vào thời gian chạy và giữ những thay đổi đó trong một ngăn xếp được quản lý bởi hoạt động.

Ví dụ, một ứng dụng tin tức có thể sử dụng một phân đoạn để hiển thị một danh sách bài viết ở bên trái và một phân đoạn khác để hiển thị một bài viết ở bên phải—cả hai phân đoạn đều xuất hiện trong một hoạt động, bên cạnh nhau, và từng phân đoạn có tập phương pháp gọi lại vòng đời riêng và xử lý các sự kiện nhập liệu người dùng riêng của mình. Vì thế, thay vì sử dụng một hoạt động để chọn một bài viết và một hoạt động khác để đọc bài viết, người dùng có thể chọn một bài viết và đọc nó trong cùng hoạt động, như được minh họa trong bố trí máy tính bảng trong hình 1.

Bạn nên thiết kế từng phân đoạn như một thành phần hoạt động dạng mô-đun và có thể sử dụng lại. Đó là bởi mỗi phân đoạn sẽ định nghĩa bố trí và hành vi của chính nó với các phương pháp gọi lại vòng đời của chính nó, bạn có thể bao gồm một phân đoạn trong nhiều hoạt động, vì thế bạn nên thiết kế để tái sử dụng và tránh trực tiếp thao tác một phân đoạn từ một phân đoạn khác. Điều này đặc biệt quan trọng vì một phân đoạn mô-đun cho phép bạn thay đổi kết hợp phân đoạn của mình cho các kích cỡ màn hình khác nhau. Khi thiết kế ứng dụng của bạn để hỗ trợ cả máy tính bảng và thiết bị cầm tay, bạn có thể sử dụng lại phân đoạn của mình trong các cấu hình bố trí khác nhau nhằm tối ưu hóa trải nghiệm người dùng dựa trên không gian màn hình có sẵn. Ví dụ, trên một thiết bị cầm tay, có thể cần phải tách riêng các phân đoạn để cung cấp một UI đơn bảng khi mà không thể làm vừa khít nhiều hơn một phân đoạn trong cùng hoạt động.

Hình 1. Ví dụ về cách hai mô-đun UI được định nghĩa bởi các phân đoạn có thể được kết hợp thành một hoạt động đối với thiết kế máy tính bảng, nhưng được tách riêng đối với thiết kế thiết bị cầm tay.

Ví dụ—để tiếp tục với ví dụ về ứng dụng tin tức—ứng dụng có thể nhúng hai phân đoạn trong Hoạt động A, khi đang chạy trên một thiết bị có kích cỡ máy tính bảng. Tuy nhiên, trên một màn hình kích cỡ thiết bị cầm tay, không có đủ khoảng trống cho cả hai phân đoạn, vì thế Hoạt động A chỉ bao gồm phân đoạn cho danh sách bài viết, và khi người dùng chọn một bài viết, nó sẽ khởi động Hoạt động B, hoạt động này chứa phân đoạn thứ hai là đọc bài viết. Vì thế, ứng dụng hỗ trợ cả máy tính bảng và thiết bị cầm tay bằng cách sử dụng lại các phân đoạn theo các cách kết hợp khác nhau như được minh họa trong hình 1.

Để biết thêm thông tin về việc thiết kế ứng dụng của bạn bằng các cách kết hợp phân đoạn khác nhau cho cấu hình màn hình khác nhau, hãy xem hướng dẫn Hỗ trợ Máy tính bảng và Thiết bị cầm tay.

Tạo một Phân đoạn

Hình 2. Vòng đời của một phân đoạn (trong khi hoạt động của nó đang chạy).

Để tạo một phân đoạn, bạn phải tạo một lớp con của Fragment (hoặc một lớp con hiện tại của nó). Lớp Fragment có mã trông rất giống một Activity. Nó chứa các phương pháp gọi lại tương tự như hoạt động, chẳng hạn như onCreate(), onStart(), onPause(), và onStop(). Trên thực tế, nếu bạn đang chuyển đổi một ứng dụng Android hiện tại để sử dụng các phân đoạn, bạn có thể chỉ cần di chuyển mã khỏi các phương pháp gọi lại của hoạt động của bạn vào các phương pháp gọi lại tương ứng của phân đoạn của bạn.

Thường thì ít nhất bạn nên triển khai các phương pháp vòng đời sau:

onCreate()
Hệ thống sẽ gọi phương pháp này khi tạo phân đoạn. Trong triển khai của mình, bạn nên khởi tạo các thành phần thiết yếu của phân đoạn mà bạn muốn giữ lại khi phân đoạn bị tạm dừng hoặc dừng hẳn, sau đó tiếp tục.
onCreateView()
Hệ thống sẽ gọi phương pháp này khi đến lúc phân đoạn vẽ giao diện người dùng của nó lần đầu tiên. Để vẽ một UI cho phân đoạn của mình, bạn phải trả về một View từ phương pháp này, đây là gốc của bố trí phân đoạn của bạn. Bạn có thể trả về giá trị rỗng nếu phân đoạn không cung cấp UI.
onPause()
Hệ thống gọi phương pháp này là dấu hiệu đầu tiên về việc người dùng đang rời khỏi phân đoạn (mặc dù không phải lúc nào cũng có nghĩa rằng phân đoạn đang bị hủy). Trường hợp này thường là khi bạn định thực hiện bất kỳ thay đổi nào vẫn cần có hiệu lực ngoài phiên của người dùng hiện thời (vì người dùng có thể không quay lại).

Phần lớn ứng dụng nên triển khai ít nhất ba phương pháp sau đối với mọi phân đoạn, nhưng có một vài phương pháp gọi lại khác mà bạn cũng nên sử dụng để xử lý các giai đoạn khác nhau trong vòng đời của phân đoạn. Tất cả phương pháp gọi lại vòng đời được đề cập chi tiết hơn trong phần về Xử lý Vòng đời của Phân đoạn.

Cũng có một vài lớp con mà bạn có thể muốn mở rộng thay vì lớp cơ bản Fragment:

DialogFragment
Hiển thị một hộp thoại trôi nổi. Sử dụng lớp này để tạo một hộp thoại là một phương án hay cho việc sử dụng các phương pháp trình trợ giúp hộp thoại trong lớp Activity, vì bạn có thể kết hợp một hộp thoại phân đoạn vào ngăn xếp của các phân đoạn được quản lý bởi hoạt động, cho phép người dùng trả về một phân đoạn bị bỏ.
ListFragment
Hiển thị một danh sách các mục được quản lý bởi một trình điều hợp (chẳng hạn như một SimpleCursorAdapter), tương tự như ListActivity. Nó cung cấp một vài phương pháp để quản lý một dạng xem danh sách, chẳng hạn như phương pháp gọi lại onListItemClick() để xử lý các sự kiện nhấp.
PreferenceFragment
Hiển thị một phân cấp các đối tượng Preference dưới dạng một danh sách, tương tự như PreferenceActivity. Điều này hữu ích khi tạo một hoạt động "thiết đặt" cho ứng dụng của bạn.

Thêm một giao diện người dùng

Phân đoạn thường được sử dụng như một phần giao diện người dùng của hoạt động và đóng góp bố trí của chính nó cho hoạt động.

Để cung cấp một bố trí cho một phân đoạn, bạn phải triển khai phương pháp gọi lại onCreateView(), phương pháp này được hệ thống Android gọi khi đến lúc phân đoạn vẽ bố trí của nó. Việc bạn triển khai phương pháp này phải trả về một View là phần gốc cho bố trí phân đoạn của bạn.

Lưu ý: Nếu phân đoạn của bạn là một lớp con của ListFragment, triển khai mặc định sẽ trả về một ListView từ onCreateView(), vì thế bạn không cần triển khai nó.

Để trả về một bố trí từ onCreateView(), bạn có thể bung nó từ một tài nguyên bố trí được định nghĩa trong XML. Để giúp bạn làm vậy, onCreateView() cung cấp một đối tượng LayoutInflater.

Ví dụ, sau đây là một lớp con của Fragment với chức năng nạp một bố trí từ tệp example_fragment.xml:

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

Tham số container được chuyển tới onCreateView()ViewGroup mẹ (tức bố trí của hoạt động), trong đó bố trí phân đoạn của bạn sẽ được chèn vào. Tham số savedInstanceState là một Bundle có chức năng cung cấp dữ liệu về thực thể trước đó của phân đoạn, nếu phân đoạn đang được tiếp tục (việc khôi phục trạng thái được bàn kỹ hơn trong phần về Xử lý Vòng đời của Phân đoạn).

Phương pháp inflate() có ba tham đối:

  • ID tài nguyên của bố trí mà bạn muốn bung.
  • ViewGroup là mẹ của bố trí được bung. Việc chuyển container có vai trò quan trọng để hệ thống áp dụng các tham số bố trí cho dạng xem gốc của bố trí được bung, được quy định bởi dạng xem mẹ là nơi mà nó diễn ra trong đó.
  • Một boolean cho biết bố trí được bung có nên được gắn với ViewGroup (tham số thứ hai) trong khi bung hay không. (Trong trường hợp này, điều này là sai vì hệ thống đã đang chèn bố trí được bung vào container—việc chuyển đúng sẽ tạo ra một nhóm dạng xem thừa trong bố trí cuối cùng.)

Giờ bạn đã thấy cách tạo một phân đoạn nhằm cung cấp một bố trí. Tiếp theo, bạn cần thêm phân đoạn vào hoạt động của mình.

Thêm một phân đoạn vào một hoạt động

Thường thì một phân đoạn đóng góp một phần UI vào hoạt động chủ, nó được nhúng như một phần trong phân cấp dạng xem tổng thể của hoạt động. Có hai cách mà bạn có thể thêm một phân đoạn vào bố trí của hoạt động:

  • Khai báo phân đoạn bên trong tệp bố trí của hoạt động.

    Trong trường hợp này, bạn có thể chỉ định các tính chất bố trí cho phân đoạn như thể nó là một dạng xem. Ví dụ, sau đây là tệp bố trí cho một hoạt động có hai phân đoạn:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment android:name="com.example.news.ArticleListFragment"
                android:id="@+id/list"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
        <fragment android:name="com.example.news.ArticleReaderFragment"
                android:id="@+id/viewer"
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
    </LinearLayout>
    

    Thuộc tính android:name trong &lt;fragment&gt; sẽ chỉ định lớp Fragment để khởi tạo trong bố trí.

    Khi hệ thống tạo bố trí hoạt động này, nó sẽ khởi tạo từng phân đoạn được chỉ định trong bố trí và gọi ra phương pháp onCreateView() cho từng phân đoạn, để truy xuất bố trí của từng phân đoạn. Hệ thống sẽ chèn View được trả về bởi phân đoạn trực tiếp thế chỗ phần tử &lt;fragment&gt;.

    Lưu ý: Mỗi phân đoạn yêu cầu một mã định danh duy nhất mà hệ thống có thể sử dụng để khôi phục phân đoạn nếu hoạt động bị khởi động lại (và bạn có thể sử dụng để nắm bắt phân đoạn sẽ thực hiện giao tác, chẳng hạn như gỡ bỏ nó). Có ba cách để cung cấp ID cho một phân đoạn:

    • Cung cấp thuộc tính android:id với một ID duy nhất.
    • Cung cấp thuộc tính android:tag với một xâu duy nhất.
    • Nếu bạn không cung cấp được thuộc tính nào, hệ thống sẽ sử dụng ID của dạng xem của bộ chứa.
  • Hoặc, bằng cách lập trình, thêm phân đoạn vào một ViewGroup hiện hữu.

    Vào bất cứ lúc nào trong khi hoạt động của bạn đang chạy, bạn có thể thêm phân đoạn vào bố trí hoạt động của mình. Bạn chỉ cần chỉ định một ViewGroup là nơi mà bạn sẽ đặt phân đoạn vào.

    Để thực hiện giao tác phân đoạn trong hoạt động của mình (chẳng hạn như thêm, gỡ bỏ, hay thay thế một phân đoạn), bạn phải sử dụng các API từ FragmentTransaction. Bạn có thể nhận một thực thể của FragmentTransaction từ Activity của mình như sau:

    FragmentManager fragmentManager = getFragmentManager()
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    

    Sau đó, bạn có thể thêm một phân đoạn bằng cách sử dụng phương pháp add(), chỉ định phân đoạn sẽ thêm và dạng xem mà bạn sẽ chèn nó vào. Ví dụ:

    ExampleFragment fragment = new ExampleFragment();
    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
    

    Tham đối đầu tiên được chuyển cho add()ViewGroup, là nơi mà phân đoạn sẽ được đặt vào, được chỉ định bởi ID tài nguyên, và tham đối thứ hai là phân đoạn cần thêm.

    Sau khi bạn đã thực hiện các thay đổi của mình bằng FragmentTransaction, bạn phải gọi commit() để các thay đổi có hiệu lực.

Thêm một phân đoạn không có UI

Các ví dụ nêu trên cho biết cách thêm một phân đoạn vào hoạt động của bạn để cung cấp một UI. Tuy nhiên, bạn cũng có thể sử dụng một phân đoạn để cung cấp một hành vi chạy ngầm cho hoạt động mà không cần đưa UI bổ sung.

Để thêm một phân đoạn không có UI, hãy thêm phân đoạn từ hoạt động đang bằng cách sử dụng add(Fragment, String) (cung cấp một "tag" xâu duy nhất cho phân đoạn , thay vì một ID dạng xem). Làm vậy sẽ thêm phân đoạn, nhưng vì không liên kết với một dạng xem trong bố trí hoạt động, nó sẽ không nhận được lệnh gọi tới onCreateView(). Vì thế, bạn không cần triển khai phương pháp đó.

Việc cung cấp tag xâu cho phân đoạn không chỉ áp dụng cho các phân đoạn không có UI—bạn cũng có thể cung cấp tag xâu cho phân đoạn có UI—nhưng nếu phân đoạn không có UI, khi đó, tag xâu là cách duy nhất để nhận biết nó. Nếu sau này bạn muốn nhận phân đoạn từ hoạt động, bạn cần sử dụng findFragmentByTag().

Để biết ví dụ về hoạt động sử dụng phân đoạn như một trình thực hiện nền, không có UI, hãy xem mẫu FragmentRetainInstance.java, mẫu này có trong các mẫu SDK (có sẵn thông qua Trình quản lý SDK Android) và nằm trên hệ thống của bạn như là <sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java.

Quản lý Phân đoạn

Để quản lý các phân đoạn trong hoạt động của mình, bạn cần sử dụng FragmentManager. Để có nó, hãy gọi getFragmentManager() từ hoạt động của bạn.

Một số việc bạn có thể làm với FragmentManager bao gồm:

  • Nhận các phân đoạn tồn tại trong hoạt động, bằng findFragmentById() (đối với các phân đoạn cung cấp UI trong bố trí hoạt động) hoặc findFragmentByTag() (đối với các phân đoạn có hoặc không cung cấp UI).
  • Lấy phân đoạn ra khỏi ngăn xếp, bằng popBackStack() (mô phỏng một câu lệnh Quay lại của người dùng).
  • Đăng ký một đối tượng theo dõi cho những thay đổi đối với ngăn xếp, bằng addOnBackStackChangedListener().

Để biết thêm thông tin về những phương pháp này và phương pháp khác, hãy tham khảo tài liệu lớp FragmentManager.

Như minh họa trong phần trước, bạn cũng có thể sử dụng FragmentManager để mở một FragmentTransaction, nó cho phép bạn thực hiện các giao tác, ví dụ như thêm hoặc gỡ bỏ phân đoạn.

Thực hiện Giao tác Phân đoạn

Một tính năng tuyệt vời khi sử dụng phân đoạn trong hoạt động của bạn đó là khả năng thêm, gỡ bỏ, thay thế, và thực hiện các hành động khác với chúng, để hồi đáp lại tương tác của người dùng. Mỗi tập hợp thay đổi mà bạn thực thi cho hoạt động được gọi là một giao tác và bạn có thể thực hiện một giao tác bằng cách sử dụng các API trong FragmentTransaction. Bạn cũng có thể lưu từng giao tác vào một ngăn xếp được quản lý bởi hoạt động, cho phép người dùng điều hướng ngược lại thông qua những thay đổi phân đoạn (tương tự như điều hướng ngược lại thông qua hoạt động).

Bạn có thể thu được một thực thể của FragmentTransaction từ FragmentManager như sau:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

Mỗi giao tác là một tập hợp những thay đổi mà bạn muốn thực hiện tại cùng thời điểm. Bạn có thể thiết lập tất cả thay đổi mà mình muốn thực hiện đối với một giao tác cho trước bằng cách sử dụng các phương pháp như add(), remove(), và replace(). Sau đó, để áp dụng giao tác cho hoạt động, bạn phải gọi commit().

Trước khi bạn gọi commit(), tuy nhiên, bạn có thể muốn gọi addToBackStack(), để thêm giao tác vào một ngăn xếp của các giao tác phân đoạn. Ngăn xếp này được quản lý bởi hoạt động và cho phép người dùng trở về trạng thái phân đoạn trước đó, bằng cách nhấp nút Quay lại.

Ví dụ, sau đây là cách bạn có thể thay thế phân đoạn này bằng phân đoạn khác, và giữ nguyên trạng thái trước đó của ngăn xếp:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

Trong ví dụ này, newFragment thay thế mọi phân đoạn (nếu có) hiện đang ở trong bộ chứa bố trí được nhận biết bởi ID R.id.fragment_container. Bằng cách gọi addToBackStack(), giao tác thay thế được lưu vào ngăn xếp, vì thế người dùng có thể đảo ngược giao tác và mang giao tác trước đó trở lại bằng cách nhấn nút Quay lại.

Nếu bạn thêm nhiều thay đổi vào giao tác (chẳng hạn như một add() khác hoặc remove()) và gọi addToBackStack(), khi đó, tất cả thay đổi được áp dụng trước khi bạn gọi commit() đều được thêm vào ngăn xếp như một giao tác riêng lẻ và nút Quay lại sẽ đảo ngược tất cả cùng nhau.

Thứ tự mà bạn thêm thay đổi vào một FragmentTransaction không quan trọng, ngoại trừ:

  • Bạn phải gọi commit() cuối cùng
  • Nếu bạn thêm nhiều phân đoạn vào cùng bộ chứa, khi đó thứ tự mà bạn thêm chúng sẽ xác định thứ tự chúng xuất hiện trong phân cấp dạng xem

Nếu bạn không gọi addToBackStack() khi thực hiện một giao tác để xóa một phân đoạn, khi đó, phân đoạn đó sẽ bị hủy khi giao tác được thực hiện và người dùng không thể điều hướng trở lại nó. Trong khi đó, nếu bạn gọi addToBackStack() khi gỡ bỏ một phân đoạn, khi đó phân đoạn bị dừng và sẽ được khôi phục nếu người dùng điều hướng trở lại.

Mẹo: Với mỗi giao tác phân đoạn, bạn có thể áp dụng một hoạt ảnh chuyển tiếp bằng cách gọi setTransition() trước khi thực thi.

Việc gọi commit() không thực hiện giao tác ngay lập tức. Thay vào đó, nó lập lịch biểu để chạy trên luồng UI của hoạt động (luồng "chính") ngay khi luồng có thể làm vậy. Tuy nhiên, nếu cần, bạn có thể gọi executePendingTransactions() từ luồng UI của mình để ngay lập tức thực hiện các giao tác được gửi bởi commit(). Làm vậy thường không cần thiết trừ khi giao tác đó là phụ thuộc cho các tác vụ ở những luồng khác.

Chú ý: Bạn có thể thực thi một giao tác bằng cách sử dụng commit() chỉ trước khi hoạt động lưu trạng thái của nó (khi người dùng rời khỏi hoạt động). Nếu bạn định thực thi sau thời điểm đó sẽ phát sinh một lỗi ngoại lệ. Nguyên nhân là vì trạng thái sau khi thực thi có thể bị mất nếu hoạt động cần được khôi phục. Đối với những trường hợp mà bạn có thể mất thực thi, hãy sử dụng commitAllowingStateLoss().

Giao tiếp với Hoạt động

Mặc dù Fragment được triển khai như một đối tượng độc lập với Activity và có thể được sử dụng bên trong nhiều hoạt động, một thực thể đã cho của phân đoạn sẽ được gắn kết trực tiếp với hoạt động chứa nó.

Cụ thể, phân đoạn có thể truy cập thực thể Activity bằng getActivity() và dễ dàng thực hiện các tác vụ như tìm một dạng xem trong bố trí hoạt động:

View listView = getActivity().findViewById(R.id.list);

Tương tự, hoạt động của bạn có thể gọi ra các phương pháp trong phân đoạn bằng cách thu được một tham chiếu tới Fragment từ FragmentManager, bằng cách sử dụng findFragmentById() hoặc findFragmentByTag(). Ví dụ:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

Tạo gọi lại sự kiện cho hoạt động

Trong một số trường hợp, bạn có thể cần một phân đoạn để chia sẻ sự kiện với hoạt động. Một cách hay để làm điều này đó là định nghĩa một giao diện gọi lại bên trong phân đoạn và yêu cầu hoạt động chủ triển khai nó. Khi hoạt động nhận được một lệnh gọi lại thông qua giao diện, nó có thể chia sẻ thông tin với các phân đoạn khác trong bố trí nếu cần.

Ví dụ, nếu một ứng dụng tin tức có hai phân đoạn trong một hoạt động—một để hiển thị danh sách bài viết (phân đoạn A) và một để hiển thị một bài viết (phân đoạn B)—khi đó, phân đoạn A phải thông báo với hoạt động khi nào thì một mục danh sách được chọn để nó có thể yêu cầu phân đoạn B hiển thị bài viết đó. Trong trường hợp này, giao diện OnArticleSelectedListener sẽ được khai báo bên trong phân đoạn A:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

Khi đó, hoạt động lưu trữ phân đoạn sẽ triển khai giao diện OnArticleSelectedListener và khống chế onArticleSelected() để thông báo với phân đoạn B về sự kiện từ phân đoạn A. Để đảm bảo rằng hoạt động chủ triển khai giao diện này, phương pháp gọi lại onAttach() của phân đoạn A (mà hệ thống gọi khi thêm phân đoạn vào hoạt động) sẽ khởi tạo một thực thể của OnArticleSelectedListener bằng cách đổi kiểu Activity mà được chuyển vào onAttach():

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

Nếu hoạt động chưa triển khai giao diện, khi đó phân đoạn sẽ đưa ra lỗi ClassCastException. Nếu thành công, thành viên mListener giữ một tham chiếu tới triển khai OnArticleSelectedListenercủa hoạt động, sao cho phân đoạn A có thể chia sẻ sự kiện với hoạt động bằng cách gọi các phương pháp được định nghĩa bởi giao diện OnArticleSelectedListener. Ví dụ, nếu phân đoạn A là một phần mở rộng của ListFragment, mỗi lần người dùng nhấp vào một mục danh sách, hệ thống sẽ gọi ra onListItemClick() trong phân đoạn, và nó lại gọi onArticleSelected() để chia sẻ sự kiện với hoạt động:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

Tham số id được chuyển vào onListItemClick() là ID hàng của mục được nhấp, nó được sử dụng bởi hoạt động (hoặc phân đoạn kia) để tải bài viết từ ContentProvider của ứng dụng.

Bạn có thể xem thêm thông tin về cách sử dụng một trình cung cấp nội dung trong tài liệu Trình cung cấp Nội dung.

Thêm mục vào Thanh Hành động

Phân đoạn của bạn có thể đóng góp các mục menu vào Menu Tùy chọn của hoạt động (và tiếp đó là cả Thanh Hành động) bằng cách triển khai onCreateOptionsMenu(). Tuy nhiên, để phương pháp này nhận lệnh gọi, bạn phải gọi setHasOptionsMenu() trong khi onCreate(), để cho biết rằng phân đoạn sẽ muốn thêm mục vào Menu Tùy chọn (nếu không, phân đoạn sẽ không nhận được lệnh gọi tới onCreateOptionsMenu()).

Bất kỳ mục nào mà bạn thêm vào Menu Tùy chọn sau đó từ phân đoạn đều được nối với các mục menu hiện tại. Phân đoạn cũng nhận các lệnh gọi lại tới onOptionsItemSelected() khi một mục menu được chọn.

Bạn cũng có thể đăng ký một dạng xem trong bố trí phân đoạn của mình để cung cấp một menu ngữ cảnh bằng cách gọi registerForContextMenu(). Khi người dùng mở menu ngữ cảnh, phân đoạn nhận một lệnh gọi tới onCreateContextMenu(). Khi người dùng chọn một mục, phân đoạn nhận được một lệnh gọi tới onContextItemSelected().

Lưu ý: Mặc dù phân đoạn của bạn nhận được một lệnh gọi khi chọn mục đối với từng mục menu mà nó thêm, trước tiên hoạt động sẽ nhận phương pháp gọi lại tương ứng khi người dùng chọn một mục menu. Nếu việc triển khai gọi lại khi chọn mục của hoạt động không xử lý mục được chọn, khi đó sự kiện được chuyển sang phương pháp gọi lại của phân đoạn. Điều này đúng đối với Menu Tùy chọn và các menu ngữ cảnh.

Để biết thêm thông tin về các menu, xem các hướng dẫn cho nhà phát triển MenuThanh Hành động.

Xử lý Vòng đời của Phân đoạn

Hình 3. Ảnh hưởng của vòng đời hoạt động tới vòng đời của phân đoạn.

Việc quản lý vòng đời của một phân đoạn rất giống với quản lý vòng đời của một hoạt động. Giống như hoạt động, phân đoạn có thể tồn tại ở ba trạng thái:

Tiếp tục
Phân đoạn hiển thị trong hoạt động đang chạy.
Tạm dừng
Một hoạt động khác ở trong tiền cảnh và có tiêu điểm, nhưng hoạt động mà phân đoạn này nằm trong vẫn hiển thị (hoạt động tiền cảnh mờ một phần hoặc không che phủ toàn bộ màn hình).
Dừng
Phân đoạn không hiển thị. Hoặc là hoạt động chủ đã bị dừng hoặc phân đoạn đã được gỡ bỏ khỏi hoạt động, nhưng được thêm vào ngăn xếp. Phân đoạn dừng vẫn còn hoạt động (tất cả thông tin về trạng thái và thành viên đều được hệ thống giữ lại). Tuy nhiên, nó không còn hiển thị với người dùng nữa và sẽ bị tắt bỏ nếu hoạt động bị tắt bỏ.

Cũng như một hoạt động, bạn có thể giữ lại trạng thái của một phân đoạn bằng cách sử dụng Bundle, trong trường hợp tiến trình của hoạt động bị tắt bỏ và bạn cần khôi phục trạng thái của phân đoạn khi hoạt động được tạo lại. Bạn có thể lưu trạng thái trong phương pháp gọi lại onSaveInstanceState() của phân đoạn và khôi phục nó trong hoặc onCreate(), onCreateView(), hoặc onActivityCreated(). Để biết thêm thông tin về việc lưu trạng thái, xem tài liệu Hoạt động .

Sự khác nhau quan trọng nhất trong vòng đời giữa một hoạt động và một phân đoạn đó là cách chúng được lưu trữ trong ngăn xếp tương ứng. Hoạt động được đặt vào một ngăn xếp gồm nhiều hoạt động , được quản lý bởi hệ thống theo mặc định khi bị dừng (sao cho người dùng có thể điều hướng lại nó bằng nút Quay lại như được đề cập trong Tác vụ và Ngăn xếp). Tuy nhiên, phân đoạn chỉ được đặt vào một ngăn xếp do hoạt động chủ quản lý khi bạn yêu cầu rõ ràng rằng trường hợp đó phải được lưu bằng cách gọi addToBackStack() trong một giao tác gỡ bỏ phân đoạn.

Nếu không thì việc quản lý vòng đời của phân đoạn rất giống với việc quản lý vòng đời của hoạt động. Vì thế, những nội dung áp dụng cho quản lý vòng đời của hoạt động cũng áp dụng cho phân đoạn. Tuy nhiên, việc mà bạn cũng cần phải hiểu đó là cách vòng đời của hoạt động ảnh hưởng tới vòng đời của phân đoạn.

Chú ý: Nếu bạn cần một đối tượng Context trong Fragmentcủa mình, bạn có thể gọi getActivity(). Tuy nhiên, nhớ chỉ được gọi getActivity() khi phân đoạn được gắn với một hoạt động. Khi phân đoạn chưa được gắn, hoặc bị gỡ trong khi kết thúc vòng đời của nó, getActivity() sẽ trả về rỗng.

Phối hợp với vòng đời của hoạt động

Vòng đời của hoạt động mà phân đoạn có ở trong đó sẽ trực tiếp ảnh hưởng tới vòng đời của phân đoạn , sao cho mỗi lệnh gọi lại vòng đời cho hoạt động đó sẽ dẫn tới một lệnh gọi lại tương tự cho từng phân đoạn. Ví dụ, khi hoạt động nhận được onPause(), mỗi phân đoạn trong hoạt động sẽ nhận được onPause().

Tuy nhiên, các phân đoạn có thêm một vài lệnh gọi lại vòng đời nhằm xử lý tương tác duy nhất với hoạt động để thực hiện các hành động như xây dựng và hủy UI của phân đoạn. Những phương pháp gọi lại bổ sung này là:

onAttach()
Được gọi khi phân đoạn đã được liên kết với hoạt động Activity được chuyển ở đây).
onCreateView()
Được gọi khi tạo phân cấp dạng xem được liên kết với phân đoạn.
onActivityCreated()
Được gọi khi phương pháp onCreate() của hoạt động đã trả về.
onDestroyView()
Được gọi khi phân cấp dạng xem được liên kết với phân đoạn đang được gỡ bỏ.
onDetach()
Được gọi khi phân đoạn đang được bỏ liên kết khỏi hoạt động.

Tiến trình vòng đời của một phân đoạn, do bị ảnh hưởng bởi hoạt động chủ của nó, được minh họa bởi hình 3. Trong hình này, bạn có thể thấy cách thức mỗi trạng thái nối tiếp nhau của hoạt động sẽ xác định các phương pháp gọi lại nào mà một phân đoạn có thể nhận được. Ví dụ, khi hoạt động đã nhận được lệnh gọi lại onCreate() của nó, phân đoạn trong hoạt động sẽ nhận được không quá lệnh gọi lại onActivityCreated().

Sau khi hoạt động đạt trạng thái tiếp tục, bạn có thể tự do thêm và gỡ bỏ phân đoạn vào hoạt động. Vì thế, chỉ trong khi hoạt động ở trạng thái tiếp tục thì vòng đời của một phân đoạn mới có thể thay đổi độc lập.

Tuy nhiên, khi hoạt động rời khỏi trạng thái tiếp tục, phân đoạn lại bị hoạt động đẩy qua vòng đời của mình.

Ví dụ

Để kết hợp mọi nội dung được đề cập trong tài liệu này, sau đây là một ví dụ về hoạt động sử dụng hai phân đoạn để tạo một bố trí hai bảng. Hoạt động bên dưới bao gồm một phân đoạn để hiển thị danh sách các vở kịch của Shakespeare và một phân đoạn khác để hiển thị tóm tắt về vở kịch khi được chọn từ danh sách. Nó cũng minh họa cách cung cấp các cấu hình phân đoạn khác nhau, dựa trên cấu hình màn hình.

Lưu ý: Mã nguồn hoàn chỉnh cho hoạt động này có sẵn trong FragmentLayout.java.

Hoạt động chính áp dụng một bố trí theo cách thông thường, trong onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

Bố trí được áp dụng là fragment_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

Khi sử dụng bố trí này, hệ thống sẽ khởi tạo TitlesFragment (liệt kê tên các vở kịch) ngay khi hoạt động nạp bố trí, trong khi FrameLayout (nơi sẽ xuất hiện phân đoạn hiển thị tóm tắt về vở kịch) chiếm khoảng trống phía bên phải của màn hình, nhưng ban đầu vẫn trống. Như bạn sẽ thấy bên dưới, mãi tới khi người dùng chọn một mục từ danh sách thì một phân đoạn mới được đặt vào FrameLayout.

Tuy nhiên, không phải tất cả cấu hình màn hình đều đủ rộng để hiển thị cả danh sách các vở kịch và tóm tắt bên cạnh nhau. Vì thế, bố trí trên chỉ được sử dụng cho cấu hình màn hình khổ ngang bằng cách lưu nó dưới dạng res/layout-land/fragment_layout.xml.

Vì thế, khi màn hình hướng đứng, hệ thống sẽ áp dụng bố trí sau, nó được lưu tại res/layout/fragment_layout.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

Bố trí này chỉ bao gồm TitlesFragment. Điều này có nghĩa là, khi thiết bị ở hướng đứng, chỉ danh sách tên vở kịch được hiển thị. Vì thế, khi người dùng nhấp vào một mục danh sách trong cấu hình này, ứng dụng sẽ bắt đầu một hoạt động mới để hiển thị tóm tắt, thay vì tải một phân đoạn thứ hai.

Tiếp theo, bạn có thể thấy cách hoàn thành điều này trong các lớp phân đoạn. Đầu tiên là TitlesFragment, hiển thị danh sách tên các vở kịch của Shakespeare. Phân đoạn này sẽ mở rộng ListFragment và dựa vào nó để xử lý hầu hết công việc về dạng xem danh sách.

Khi bạn kiểm tra đoạn mã này, hãy để ý rằng có hai hành vi có thể khi người dùng nhấp vào một mục danh sách: phụ thuộc vào bố trí nào trong hai bố trí đang hiện hoạt, nó có thể hoặc tạo và hiển thị một phân đoạn mới để hiển thị chi tiết trong cùng hoạt động (thêm phân đoạn vào FrameLayout), hoặc bắt đầu một hoạt động mới (tại đó phân đoạn có thể được hiển thị).

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, details);
                }
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

Phân đoạn thứ hai, DetailsFragment sẽ hiển thị tóm tắt vở kịch cho mục được chọn từ danh sách trong TitlesFragment:

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

Nhớ lại ở lớp TitlesFragment rằng, nếu người dùng nhấp vào một mục danh sách và bố trí hiện tại không có dạng xem R.id.details (là nơi mà DetailsFragment thuộc về), khi đó, ứng dụng sẽ bắt đầu hoạt động DetailsActivity để hiển thị nội dung của mục đó.

Sau đây là DetailsActivity, nó chỉ đơn thuần nhúng DetailsFragment để hiển thị tóm tắt vở kịch được chọn khi màn hình ở hướng đứng:

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

Lưu ý rằng hoạt động này tự kết thúc nếu cấu hình là khổ ngang, sao cho hoạt động chính có thể chiếm lấy và hiển thị DetailsFragment bên cạnh TitlesFragment. Điều này có thể xảy ra nếu người dùng bắt đầu DetailsActivity ở dạng hướng đứng, nhưng sau đó xoay thành khổ ngang (làm vậy sẽ bắt đầu lại hoạt động hiện tại).

Để biết thêm mẫu sử dụng phân đoạn (và toàn bộ tệp nguồn cho ví dụ này), hãy xem ứng dụng mẫu API Demos có sẵn trong ApiDemos (có thể tải xuống từ Thành phần SDK Mẫu).