+1

[Translation] Các khái niệm cơ bản về Fragment trong Android( Phần 3)

Tiếp theo phần trước , phần này mình sẽ đề cập đến cách giao tiếp với Activity

(Nội dung được mình tham khảo chủ yếu trên trang https://developer.android.com/guide/components/fragments.html )

Communicating with the Activity

Mặc dù fragment được cài đặt như 1 đối tượng độc lập đối với Activity và có thể sử dụng trong nhiều Activity , nhưng 1 instance của 1 fragment lại gắn trực tiếp với Activity chứa nó.

Đặc biệt , 1 fragment có thể truy cập trực tiếp đến 1 instance của Activity với getActivity() và dễ dàng thực hiện những task như find view trong activity layout

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

Chẳng hạn , activity của bạn gọi những hàm bằng cách truy cập đến tham chiếu đối với Fragment từ Fragment Manager , sử dụng findViewById() hoặc findFragmentByTag() Ví dụ :

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

Creating event callbacks to the activity

Trong 1 số trường hợp , bạn có thể cần 1 fragment để chia sẻ các event với activity.Một cách tốt để làm được điều đó là định nghĩa 1 interface bên trong fragment và yêu cầu host activity implement nó .Khi activity nhận được callback thông qua interface , nó có thể chia sẻ thông tin với fragment khác trong layout nếu cần thiết . Ví dụ , Nếu 1 ứng dụng tin tức có 2 fragment trong activity , 1 để show list bài báo (fragment A) , 1 cái khác để show bài báo (fragment B), fragment A phải nói cho Activity biết khi nào list item được chọn do đó nó có thể báo cho fragment B hiển thị bài báo , Trong trường hợp này , OnArticleSelectedListener được implement ở fragment A :

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

Và activity implement OnArticleSelectedListener và override onArticleSelected() để thông báo fragment B về event từ fragment A. Để đảm bảo rằng activity implement interface này , hàm onAttach() của fragment A khởi tạo 1 instance của OnArticleSelectedListener bằng ép kiểu cho Activity và truyền 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 activity chưa được implement interface thì fragment sẽ throw ClassCastException . Nếu thành công , mListener sẽ giữ 1 reference đến OnArticleSelectedListener trong activity , do vậy fragment A cần chia sẻ event với activity bằng cách gọi hàm được định nghĩa bởi OnArticleSelectedListener .Ví dụ , nếu fragment A là 1 extention của ListFragment , mỗi khi người dùng click vào list item , hệ thống gọi onListItemClick() trong fragment , và rồi gọi onArticleSelected() để chia sẻ event với activity :

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);
 }

id được truyền vào onListItemCLick() chính là hàng ID của item được click , được activity (hoặc fragment ) dùng để lấy dữ liệu bài báo từ ContentProvider.

Adding items to the App Bar

Fragment của bạn có thể set menu item đối với Options Menu bằng cách implement onCreateOptionsMenu() .Để hàm có thể gọi được , tuy nhiên , bạn phải gọi setHasOptionsMenu() trong onCreate() , để chỉ ra rằng fragment muốn thêm item vào Options Menu (Đối với các trường hợp khác thì hàm onCreateOptionsMenu() sẽ không được gọi đến) Bất cứ item bạn thêm vào Options Menu từ fragment sẽ được thêm tiếp vào menu . Fragment cũng nhận được callback đến onOptionsItemSelected() khi 1 menu item được chọn .
Bạn cũng có thể đăng kí 1 view trong fragment layout để cung cấp context menu bằng cách gọi registerForContextMenu() . Khi người dùng mở context menu , fragment nhận được lời gọi tới onCreateContextMenu() . Khi người dùng chọn 1 item , onContextItemSelected() trong fragment sẽ được gọi .

Chú ý : Mặc dù fragment của bạn nhận callback mỗi khi người dùng chọn 1 menu item , nhưng activity mới là nơi nhận callback đầu tiên .Nếu activity không xử lí thì event sẽ được truyền tiếp cho fragment .Điều này đúng với Options Menu và context menu

Example

Để kết hợp tất cả lí thuyết ở trên với nhau , dưới đây là 1 ví dụ về 1 activity sử dụng 2 fragment để tạo thành 1 layout có 2 phần . Activity bên dưới bao gồm 1 fragment hiển thị danh sách các vở kịch của Shakespeare và 1 cái khác để hiện thị nội dung bên trong vở kịch đó . Activity chính vẫn set layout trong onCreate() như bình thường :

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

    setContentView(R.layout.fragment_layout);
}

Layout được set vào 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>

Hệ thống khởi tạo TitlesFragment (Hiển thị tiêu đề vở kịch ) cùng lúc activity load layout , Trong khi FrameLayout (nơi fragment hiển thị nội dung vở kịch) sẽ sử dụng vị trí bên phải màn hình , nhưng ban đầu nó sẽ bị trống . Như bạn nhìn bên dưới , chỉ khi người dùng chọn item thì fragment mới được đặt vào vị trí của FrameLayout Tuy nhiên , không phải cấu hình của màn hình nào cũng đủ rộng để hiển thị cùng lúc cả danh sách tiêu đề và nội dung . Vì thế layout ở trên chỉ dùng cho màn hình cấu hình kiểu landscape , bằng cách lưu nó ở res/layout-land/fragment_layout.xml Như vậy , khi màn hình là dạng portrait , hệ thống sẽ sử dụng layout được lưu ở 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>

Layout chỉ bao gồm TitlesFragment . Điều này có nghĩa là , khi device hiển thị dạng portrait , chỉ có tiêu đề được hiển thị . Do đó , khi người dùng click vào danh sách item , ứng dụng sẽ start 1 activity mới hiển thị nội dung thay vì load fragment thứ 2 . Tiếp theo , bạn có thể theo dõi fragment class được viết hoàn chỉnh dưới đây . Đầu tiên là TitlesFragment , fragment này sẽ hiển thị title .Nó được extend từ ListFragment để xử lí phần ListView. Khi bạn nhìn xuống đoạn code bên dưới , hãy lưu ý rằng có 2 khả năng khi người dùng click danh sách : tùy vào cách mà 2 layout hoạt đông , nó có thể hoặc khởi tạo và hiển thị 1 fragment mới để hiện thị chi tiết trên cùng 1 activity hoặc tạo 1 activity mới

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);
        }
    }
}

Fragment thứ 2 , DetailsFragment sẽ hiển thị nội dung vở kịch đối với mỗi item đượ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;
    }
}

Nếu người dùng click vào item và layout hiện tại(không bào gồm view R.id.details ) , ứng dụng sẽ khởi động DetailsActivity để hiện thị nội dung item

DetailsActivity , chỉ đơn giản là nhúng DetailsFragment vào để hiện thị nội dung của vở kịch được chọn khi màn hình là dạng portrait

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();
        }
    }
}

Nên nhớ rằng activity này sẽ tự kết thúc nếu cấu hình là landscape, do đó main activity cần xử lí và hiện thị DetailsFragment song song với TitlesFragment. Điều này có thể xảy ra khi người dùng mở DetailsActivity khi đang ở chế độ portrait , nhưng sau đó xoay sang landscape(điều này sẽ khiến restart activity hiện tại) Vậy là mình đã hoàn thành bài viết các khái niệm cở bản về Fragment trong Android ,nếu có phản hồi gì thì các bạn hãy comment ở dưới nhé


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí