Android Bài 32: Hiển Thị Fragment

Posted by

Chào mừng các bạn đã đến với bài học Android thứ 32, bài học về Fragment (phần tiếp theo). Đây là bài học trong chuỗi bài viết về lập trình ứng dụng Android bằng Java của Yellow Code Books.

Như vậy, kết thúc bài học hôm trước, chúng ta đã có trong tay hai Fragment. Fragment thứ nhất có tên FirstFragment đang hiển thị hai Button. Fragment thứ hai dĩ nhiên là có tên SecondFragment rồi, Fragment này đang chứa một TextView sẽ hiện thị nội dung nào đó tùy theo user sẽ nhấn Button nào ở FirstFragment.

Bài học tiếp theo này chúng ta sẽ tìm cách hiển thị hai Fragment này lên Activity, và thực hiện truyền thông tin qua lại giữa chúng nữa. Bạn sẽ thấy rằng, sử dụng Fragment không khó như bạn nghĩ đâu.

Nào hãy cùng mình xem và thực hành từng bước thông qua các kiến thức của bài học hôm nay nhé.

Các Cách Thức Hiển Thị Fragment

Trước khi chính thức thực hiện việc hiển thị, chúng ta cần hiểu xem có các cách hiển thị nào.

Bạn cũng nên biết rằng, hiện tại mình dùng từ Hiển thị. Nhưng bạn có thể hiểu chúng ta đang tìm cách sắp xếp các Fragment lên Activity, hiển thị chúng, và tương tác với chúng nữa. Tất cả những hành động trên đây mình gom lại thành hai từ Hiển thị mà thôi.

Chúng ta có hai cách để hiển thị các Fragment. Cách thứ nhất là hiển thị Fragment theo kiểu tĩnh. Cách thứ hai đương nhiên là cách hiển thị Fragment theo kiểu động rồi. Vậy thì hai cách này khác nhau ra sao?

Với cách hiển thị Fragment theo kiểu tĩnh. Nghe qua cái tên bạn đã thấy có gì đó mang ý nghĩa cố định rồi. Cố định ở đây có nghĩa là bạn phải chỉ định trước các Fragment cần hiển thị lên màn hình. Khi ứng dụng thực thi, sẽ không có chuyện từ một vùng màn hình đang hiển thị Fragment A, có thể được thay thế nó bởi Fragment B hay bất kỳ Fragment nào khác. Sự linh động của các Fragment đối với cách hiển thị như thế này không cao. Thường thì nếu bạn biết được Activity đó cần hiển thị những Fragment nào và ở vị trí nào, khi đó bạn có thể định nghĩa tĩnh các Fragment như này. Cách hiển thị này có ưu điểm là giúp bạn thiết kế ra các ứng dụng rất nhanh, vì không cần quá nhiều các dòng code. Chắc chắn bạn sẽ được làm quen với cách thức hiển thị Fragment theo kiểu này ở mục bên dưới.

Còn cách hiển thị Fragment theo kiểu động thì đương nhiên sẽ linh động hơn kiểu tĩnh rồi. Nếu bạn sử dụng cách này để hiển thị các Fragment, thông qua việc quản lý bằng Java code, bạn có thể thoải mái đặt Fragment nào đó vào một vùng không gian đã được chỉ định sẵn, hoặc lấy Fragment đó ra khỏi vùng không gian đó, hoặc thay thế bằng một Fragment khác. Cách hiển thị này giúp bạn đạt tới “cảnh giới” cao nhất của sự linh động trong việc sử dụng Fragment. Và dĩ nhiên chúng ta cũng sẽ cùng nhau thực hành hiển thị Fragment theo kiểu này ở bài học hôm nay.

Làm Quen Với Cách Hiển Thị Fragment Theo Kiểu Tĩnh

Giao diện khi sử dụng các Fragment

Chúng ta sẽ hiển thị giao diện như trên đây.

Cách này rất nhanh chóng. Bạn chỉ cần sử dụng một layout có tên là fragment để hiển thị một Fragment mà bạn mong muốn.

Layout fragment này cũng cần bạn chỉ định các thuộc tính android:layout_widthandroid:layout_height như các layout khác. Chính vì vậy bạn có thể thiết kế bao nhiêu fragment vào trong giao diện của Activity đều được, và đặt chúng vào vào bất cứ vị trí nào bạn muốn. Chính thuộc tính android:name của thẻ fragment này sẽ giúp bạn chỉ định Fragment nào cần hiển thị.

Bạn xem code sau, mình sẽ chỉnh sửa ở file activity_main.xml của TourNote. Mình tìm đến layout chứa TextViewImageView mà chúng ta đã thực hành, và thay thế hai widget này bằng hai thẻ fragment. Các UI còn lại liên quan đến Navigation Drawer mình vẫn giữ như cũ.

<!-- Content -->

<LinearLayout
	android:id="@+id/activity_main_content"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"
	tools:context="com.yellowcode.tournote.MainActivity">

	<fragment
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1"
		android:name="com.yellowcode.tournote.FirstFragment"/>

	<fragment
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="2"
		android:name="com.yellowcode.tournote.SecondFragment"/>

</LinearLayout>

Bạn có thể tự tạo một project mới rồi sửa chữa trên layout chính của project đó cũng được nhé. Nhớ là phải đảm bảo có sẵn hai Fragment đã được tạo ra ở bài hôm trước.

Rất nhanh. Bạn đã hiển thị hai Fragment vào trong một LinearLayout rồi đấy. Bạn để ý xem các giá trị android:name=”com.yellowcode.tournote.FirstFragment và android:name=”com.yellowcode.tournote.SecondFragment giúp hệ thống xác định Fragment nào cần hiển thị lên layout fragment nào.

Có lẽ bạn đang rất nôn nóng được xem diện mạo hai Fragment như thế nào. Bình thường với một số Fragment chuyên hiển thị dữ liệu, như SecondFragment, thì đến bước này bạn đã có thể thực thi ứng dụng để kiểm thử được rồi. Nhưng vì FirstFragment có định nghĩa interface OnFirstFragmentListener, và ở phương thức onAttach() của FirstFragment có kiểm tra nếu Activity chứa nó không implement interface này, thì sẽ báo lỗi. Chính vì vậy bạn phải thêm các dòng code sau vào MainActivity.java trước khi thực thi để tránh lỗi.

public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener {

    // Các thuộc tính...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Code như các bài học trước...
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        // Code như các bài học trước...
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        // Code như các bài học trước...
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Code như các bài học trước...
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Code như các bài học trước...
    }

    @Override
    public void onItemPressed(String content) {
    	// Implement phương thức trừu tượng của OnFirstFragmentListener
    }
}

Giờ thì bạn có thể thực thi ứng dụng được rồi. Đây là thành quả của chúng ta.

Giao diện kết quả khi sử dụng các Fragment

Bạn có thể thấy phần không gian màu trắng chứa hai Button ITEM 1ITEM 2 chính là giao diện của FirstFragment đấy. Còn phần không gian màu xám chứa một nội dung “Content” thì chính là giao diện của SecondFragment.

Chúng ta chỉ thực hành hiển thị các Fragment lên màn hình ở mục này thôi, chứ không đi sâu hơn với sự tương tác khi user touch lên bất kỳ Button nào ở FirstFragment. Vì thông thường cách hiển thị tĩnh như này chỉ nên dùng khi bạn muốn tổ chức nhanh các Fragment mà không đòi hỏi quá nhiều vào logic của chúng. Nếu bạn không thích cách hiển thị có phần thụ động này, thì hãy đọc tiếp mục sau nhé.

Làm Quen Với Cách Hiển Thị Fragment Theo Kiểu Động

Giao diện khi sử dụng các Fragment

Với cách hiển thị này thì giao diện của màn hình cũng sẽ không đổi. Như hình trên. Vậy sẽ động hơn như thế nào, bạn xem.

Nếu như với cách hiển thị tĩnh trên kia, bạn phải chỉ định thẻ fragment nào sẽ chứa đựng Fragment nào một cách cố định. Thì với cách hiển thị động này, bạn chỉ cần khai báo một vùng không gian nào đó sẽ chứa đựng Fragment, vùng không gian đó được khai báo bằng một FrameLayout.

Nào, vậy chúng ta cần phải thay đổi tí giao diện của activity_main.xml sao cho chứa đựng hai FrameLayout như sau.

<!-- Content -->

<LinearLayout
	android:id="@+id/activity_main_content"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
    android:orientation="vertical"
	tools:context="com.yellowcode.tournote.MainActivity">

	<FrameLayout
		android:id="@+id/firstFrame"
	    android:layout_width="match_parent"
        android:layout_height="0dp"
		android:layout_weight="1"/>

	<FrameLayout
        android:id="@+id/secondFrame"
		android:layout_width="match_parent"
	    android:layout_height="0dp"
        android:layout_weight="2"/>

</LinearLayout>

Giờ chúng ta sẽ qua MainActivity.java để thiết lập logic hiển thị và tương tác giữa các Fragment.

Chúng ta vẫn phải giữ dòng code implements FirstFragment.OnFirstFragmentListener của MainActivity và implement phương thức onItemPressed() mà bạn đã thêm vào ở mục trên. Vì như mình đã có nói, bất cứ khi nào mà bạn có thêm FirstFragment vào Activity, bạn buộc phải có các dòng code này.

Trước khi tiến hành add Fragment vào các FrameLayout mà bạn đã xây dựng sẵn, chúng ta cần làm quen một số lớp quan trọng liên quan sau.

FragmentManager

Đây là một lớp dùng để quản lý các Fragment, lớp này được tích hợp vào trong mỗi Activity để giúp các Activity có thể dễ dàng để thêm (add), xóa (remove) hoặc thay thế (replace) các Fragment ra khỏi một vùng không gian một cách linh động.

Nếu bạn có đọc kỹ về sự tương thích ngược của Fragment, thì có thể thấy có hai lớp FragmentManager mà bạn có thể cân nhắc sử dụng.

Các FragmentManager trong các gói

Mình xin nhắc lại tóm tắt cách chọn các lớp tương thích ngược như sau.

FragmentManager ở gói android.app – Được gọi thông qua phương thức getFragmentManager() của Activity. Nếu project của bạn khai báo minSdkVersion từ 11 trở lên thì cứ thoải mái sử dụng em này.
FragmentManager ở gói android.support.v4.app – Được gọi thông qua phương thức getSupportFragmentManager() của Activity. Nếu project của bạn khai báo minSdkVersion nhỏ hơn 11 thì bạn buộc phải dùng lớp quản lý này để có thể mang Fragment đến với các hệ điều hành trước Android 3.0.

Lớp này có một phương thức rất hữu dụng mà mình xin được nói trước như sau.

findFragmentById() – Khi dùng phương thức này bạn sẽ truyền vào cho nó một ID. ID này có thể là ID của thẻ fragment như với mục hiển thị tĩnh trên kia. Hoặc ID của FrameLayout như bạn mới vừa làm quen ở code trên đây. Kết quả của phương thức này sẽ trả về cho bạn một Fragment được chứa trong layout có ID mà bạn vừa cung cấp.

FragmentTransaction

Khi đã có FragmentManager, bạn bắt đầu thực hiện việc thêm, xóa, thay đổi thoải mái các Fragment dựa vào FragmentTransaction này. Bạn có thể “triệu hồi” FragmentTransaction thông qua phương thức beginTransaction() từ FragmentManager.

FragmentTransaction có các phương thức thú vị để bạn “chơi” với Fragment như sau.

add() – Khi FrameLayout còn rỗng, tức chưa chứa đựng bất kỳ Fragment nào, thì bạn có thể dùng phương thức này để add Fragment vào cho FrameLayout đó. Như bạn sắp làm quen với code add một FirstFragment sau đây.
replace() – Khi bạn muốn thay thế một Fragment đang có sẵn ở FrameLayout bằng một Fragment nào đó khác.
remove() – Khi bạn muốn gỡ bỏ Fragment ra khỏi một FrameLayout nào đó.
addToBackStack() – Khi bạn quản lý Fragment bởi các phương thức replace() hay remove() trên đây, bạn có thể sử dụng thêm phương thức addToBackStack() này. Nếu bạn gọi đến phương thức này trước khi gọi commit() sẽ được nói ở dưới, thì hệ thống sẽ đưa Fragment ở transaction hiện tại vào Back Stack. Điều này có ý nghĩa là, Fragment bị thay thế hay bị gỡ ra khỏi FrameLayout ở transaction này sẽ không bị xóa khỏi hệ thống mà vẫn còn được quản lý bên trong Back Stack (bạn sẽ được làm quen với Back Stack của Fragment ở bài học sau). Và do Fragment không bị hủy khỏi Back Stack, nên nếu user nhấn nút back sau đó, họ hoàn toàn có thể quay trở lại với Fragment trước đó đã bị gỡ ra.
commit() – Cho dù bạn có quản lý hiển thị Fragment bằng add(), replace() hay remove() thì bạn cũng phải gọi commit() cuối cùng, để FragmentTransaction biết sẽ bắt đầu thực hiện các transaction mà bạn đã ra lệnh đó.

Nếu như thông tin về FragmentManagerFragmentTransaction trên đây khó hiểu quá, thì bạn cứ tiếp tục thực hành tiếp phần sau đây. Bạn sẽ nhanh chóng hiểu rõ hơn thôi.

Chúng ta bắt đầu add FirstFragment vào trong FrameLayout có ID là firstFrame ngay khi MainActivity được hiển thị như sau.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

	drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer);
    drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
	drawerLayout.addDrawerListener(drawerToggle);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
	getSupportActionBar().setHomeButtonEnabled(true);

    FirstFragment firstFragment = new FirstFragment();
	FragmentManager fragmentManager = getSupportFragmentManager();
	FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.firstFrame, firstFragment);
	fragmentTransaction.commit();
}

Các dòng code add FirstFragment vào firstFrame là các dòng được tô sáng trên đây. Dòng đầu tiên là dòng khai báo FirstFragment. Kế tiếp bạn nên khai báo FragmentManager, vì Fragment chúng ta đang dùng đã được hệ thống tạo sẵn nằm trong gói android.support.v4.app, nên FragmentManager cũng sẽ được tạo ra thông qua phương thức getSupportFragmentManager(). Sau đó FragmentTransaction được tạo ra, và chúng ta dùng phương thức add() để add firstFragment vào firstFrame. Cuối cùng bạn phải nhớ phương thức commit(). Khi này nếu thực thi ứng dụng, bạn đã có thể trông thấy FirstFragment hiển thị rồi đấy.

Tiếp theo, do bạn đã implement phương thức trừu tượng onItemPresses() ở MainActivity, nên bạn sẽ nhận được một String khi user nhấn vào một Button nào đó ở FirstFragment. Đây là hình ảnh gợi nhớ lại các dòng code truyền một String mà bạn đã xây dựng bên FirstFragment ở bài hôm trước.

Sự kiện click ở FirstFragment

Do đó, mình sẽ căn cứ vào phương thức onItemPressed() này mà thực hiện việc khởi tạo SecondFragment và add vào FrameLayout có ID là secondFrame như sau.

@Override
public void onItemPressed(String content) {
	SecondFragment secondFragment = SecondFragment.newInstance(content);
	FragmentManager fragmentManager = getSupportFragmentManager();
	FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.secondFrame, secondFragment);
	fragmentTransaction.commit();
}

Bạn thấy, SecondFragment được tạo mới không thông qua từ khóa new như với FirstFragment, vì ở bài hôm trước chúng ta đã xây dựng (thực ra là sửa lại) phương thức khởi tạo newInstance() có kèm các value truyền vào rồi, nên bài này chúng ta sử dụng thôi. Các khởi tạo cho FragmentManagerFragmentTransaction thì vẫn như cách làm với FirstFragment trên kia. Lần này thay vì gọi phương thức add(), chúng ta dùng replace() sẽ hợp lý hơn, vì user có thể sẽ nhấn nhiều lần lên các ButtonFirstFragment, mỗi lần như vậy chúng ta cần thay thế Fragment cũ bằng Fragment mới tại secondFrame này. Và cuối cùng, phương thức commit() luôn phải được gọi.

Dưới đây là tất cả code của activity_main.xmlMainActivity.java mà bạn có thể tham khảo.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main_drawer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Content -->

    <LinearLayout
        android:id="@+id/activity_main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.yellowcode.tournote.MainActivity">

        <FrameLayout
            android:id="@+id/firstFrame"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <FrameLayout
            android:id="@+id/secondFrame"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="2"/>

    </LinearLayout>

    <android.support.design.widget.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            android:orientation="vertical">

            <!-- Header -->

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="@dimen/navigation_header_height"
                android:background="@color/colorPrimary"
                android:gravity="bottom"
                android:orientation="vertical"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:theme="@style/ThemeOverlay.AppCompat.Dark">

                <ImageView
                    android:id="@+id/activity_main_imv_avatar"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingTop="@dimen/activity_vertical_margin"
                    app:srcCompat="@mipmap/ic_launcher_round" />

                <TextView
                    android:id="@+id/activity_main_tv_user_name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingTop="@dimen/activity_vertical_margin"
                    android:text="Yellow Code"
                    android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

                <TextView
                    android:id="@+id/activity_main_tv_email"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="yellowcode.books@gmail.com" />

            </LinearLayout>

            <!-- Item Info -->

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="@dimen/navigation_item_height"
                android:layout_marginTop="@dimen/padding_tiny_plus_one"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin">

                <ImageView
                    android:layout_width="@dimen/navigation_item_icon_size"
                    android:layout_height="@dimen/navigation_item_icon_size"
                    android:src="@drawable/ic_action_info" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="@dimen/padding_extra_extra_large"
                    android:text="@string/menu_item_about_app" />

            </LinearLayout>

            <!-- Item Help -->

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="@dimen/navigation_item_height"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin">

                <ImageView
                    android:layout_width="@dimen/navigation_item_icon_size"
                    android:layout_height="@dimen/navigation_item_icon_size"
                    android:src="@drawable/ic_action_help" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="@dimen/padding_extra_extra_large"
                    android:text="@string/menu_item_help" />

            </LinearLayout>

        </LinearLayout>

    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener {

    private DrawerLayout drawerLayout;
    private ActionBarDrawerToggle drawerToggle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer);
        drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawerLayout.addDrawerListener(drawerToggle);

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeButtonEnabled(true);

        FirstFragment firstFragment = new FirstFragment();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.firstFrame, firstFragment);
        fragmentTransaction.commit();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        drawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        drawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_actions, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(drawerToggle.onOptionsItemSelected(item)) {
            return true;
        }

        switch (item.getItemId()) {
            case R.id.search:
                Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show();
                return true;
            case R.id.about:
                Intent intent = new Intent(this, ContactActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_ABOUT);
                intent.putExtras(bundle);
                startActivity(intent);
                return true;
            case R.id.help:
                intent = new Intent(this, ContactActivity.class);
                bundle = new Bundle();
                bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_HELP);
                intent.putExtras(bundle);
                startActivity(intent);
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemPressed(String content) {
        SecondFragment secondFragment = SecondFragment.newInstance(content);
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.secondFrame, secondFragment);
        fragmentTransaction.commit();
    }
}

Giờ đây bạn có thể thực thi ứng dụng, và nhấn thoải mái lên các Button để kiểm chứng được rồi nhé.

Giao diện kết quả khi sử dụng các Fragment

Làm Quen Với Cảnh Giới Cao Nhất Của Hiển Thị Fragment Theo Kiểu Động

Nếu bạn đã thành công với các bước hiển thị Fragment một cách linh động như trên đây, thì xin chúc mừng bạn, bạn đã hiểu rõ về bản chất của sử dụng Fragment rồi đó.

Tuy nhiên, cách sử dụng hoàn hảo Fragment sẽ được mình trình bày tiếp theo ở mục này.

Giao diện khi sử dụng các Fragment

Các bước sau đây chúng ta sẽ giúp cho MainActivity “thông minh” hơn. Khi biết được nếu thiết bị đang đặt xoay ngang (landscape), sẽ hiển thị hai Fragment lên cùng một màn hình, với FirstFragment bên trái và SecondFragment bên phải, khi click vào một item ở FirstFragment (hiện tại chúng ta đang giả lập là Button) thì sẽ cập nhật nội dung ở SecondFragment, kiểu hiển thị này giống như ví dụ ở mục trên thôi. Còn khi thiết bị đang được đặt đứng (portrait), thì sẽ hiển thị FirstFragment lên toàn màn hình, khi click vào một item ở FirstFragment, Activity sẽ hiển thị nội dung ở SecondFragment cũng toàn màn hình. Tất cả sẽ giống như hình minh họa trên đây.

Xây Dựng Giao Diện Cho Các Màn Hình Ngang/Dọc

Đầu tiên, chúng ta cần chỉnh sửa lại activity_main.xml. Đây là layout mặc định để hiển thị giao diện đứng cho thiết bị, nên chúng ta chỉ cần một Fragment toàn màn hình.

<!-- Content -->

<LinearLayout
	android:id="@+id/activity_main_content"
    android:layout_width="match_parent"
	android:layout_height="match_parent"
    tools:context="com.yellowcode.tournote.MainActivity">

	<FrameLayout
        android:id="@+id/contentFrame"
		android:layout_width="match_parent"
		android:layout_height="match_parent" />

</LinearLayout>

Sau đó bạn nên tạo thêm một thư mục layout cho màn hình ngang, thư mục mới này có tên và đường dẫn là res/layout-land/. Tại sao lại có tên như vậy thì bạn có thể xem lại trong cách sử dụng Alternative Resource của Android ở bài viết này nhé. Khi bạn tạo xong thư mục layout cho màn hình ngang, thì hãy copy file activity_main.xml hiện tại vào đó.

Fragment - Tạo mới Activity cho landscape

Như đã nói, chúng ta cần chỉnh sửa lại sao cho layout ở thư mục này sao cho chứa đựng hai Fragment.

<!-- Content -->

<LinearLayout
	android:id="@+id/activity_main_content"
    android:layout_width="match_parent"
	android:layout_height="match_parent"
    android:orientation="horizontal"
	tools:context="com.yellowcode.tournote.MainActivity">

	<FrameLayout
		android:id="@+id/firstFrame"
	    android:layout_width="0dp"
        android:layout_height="match_parent"
		android:layout_weight="1"/>

	<FrameLayout
		android:id="@+id/secondFrame"
	    android:layout_width="0dp"
        android:layout_height="match_parent"
		android:layout_weight="2"/>

</LinearLayout>

Bạn chú ý là các ID của các FrameLayout của cả hai layout ngang và đứng không giống nhau đâu nhé.

Hiển Thị FirstFragment

Không như mục trên kia, nhảy vào onCreate() là bạn có thể add FirstFragment vào firstFrame ngay. Bởi vì chúng ta có hai activity_main.xml, mỗi layout này chứa cách hiển thị Fragment khác nhau, do đó chúng ta phải kiểm tra xem layout hiện tại mà thiết bị đang dùng là layout ngang hay đứng, rồi mới add các Fragment tương ứng vào đúng FrameLayout. Code của onCreate() giờ đây như sau.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

	drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer);
    drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
	drawerLayout.addDrawerListener(drawerToggle);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
	getSupportActionBar().setHomeButtonEnabled(true);

	FirstFragment firstFragment = new FirstFragment();
    if (findViewById(R.id.contentFrame) != null) {
		// Found the ID of only one Fragment ==> Portrait mode
	    // Remove the existing fragment before add new one
        if (savedInstanceState != null) {
			getSupportFragmentManager().executePendingTransactions();
		    Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.contentFrame);
	        if (fragmentById != null) {
                getSupportFragmentManager().beginTransaction().remove(fragmentById).commit();
			}
		}

		// Add new one
		getSupportFragmentManager().beginTransaction().add(R.id.contentFrame, firstFragment).commit();
	} else {
        // Landscape mode
		// Remove the existing fragments before add new one
		if (savedInstanceState != null) {
	        getSupportFragmentManager().executePendingTransactions();
            Fragment firstFragmentById = getSupportFragmentManager().findFragmentById(R.id.firstFrame);
			if (firstFragmentById != null) {
		        getSupportFragmentManager().beginTransaction().remove(firstFragmentById).commit();
	        }
            Fragment secondFragmentById = getSupportFragmentManager().findFragmentById(R.id.secondFrame);
			if (secondFragmentById != null) {
		        getSupportFragmentManager().beginTransaction().remove(secondFragmentById).commit();
	        }
        }

		// Add new one
	    getSupportFragmentManager().beginTransaction().add(R.id.firstFrame, firstFragment).commit();
    }
}

Bạn có thể thấy, dòng if (findViewById(R.id.contentFrame) != null) sẽ giúp kiểm tra xem có tìm thấy layout với ID là contentFrame không, nếu có thì đích thị là activity_main.xml cho màn hình đứng rồi, khi đó chúng ta nên gọi phương thức remove() để gỡ Fragment ra khỏi FrameLayout trước khi add FirstFragment vào. Trường hợp thiết bị đang hiển thị kiểu ngang, thì nên gỡ cả hai Fragment ra khỏi hai FrameLayout của màn hình này rồi mới add FirstFragment vào. Các khởi tạo cho FragmentManagerFragmentTransaction mình viết ngắn gọn hơn. Code không có gì quá khó đúng không bạn.

Tương tự, ở sự kiện onItemPressed(), chúng ta cũng kiểm tra, nếu là màn hình đứng, thì sẽ thay thế FirstFragment bằng SecondFragment cho contentFrame. Nếu là màn hình ngang, thì hành xử giống như cách hiển thị hai Fragment cùng lúc ở mục trên kia thôi. Có một điều cần lưu ý rằng ở cách hiển thị với màn hình đứng, cần phải có thêm phương thức addToBackStack() để cho phép người dùng nhấn nút back ở thiết bị thì quay về Fragment trước đó, như mình có nói đến ở trên kia.

@Override
public void onItemPressed(String content) {
	SecondFragment secondFragment = SecondFragment.newInstance(content);
	if (findViewById(R.id.contentFrame) != null) {
        // Found the ID of only one Fragment ==> Portrait mode
		FragmentManager fragmentManager = getSupportFragmentManager();
	    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.contentFrame, secondFragment);
		fragmentTransaction.addToBackStack(null);
	    fragmentTransaction.commit();
    } else {
		// Landscape mode
	    getSupportFragmentManager().beginTransaction().replace(R.id.secondFrame, secondFragment).commit();
    }
}

Đây là code đầy đủ của MainActivity.java cho bạn tham khảo.

public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener {

    private DrawerLayout drawerLayout;
    private ActionBarDrawerToggle drawerToggle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer);
        drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawerLayout.addDrawerListener(drawerToggle);

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeButtonEnabled(true);

        FirstFragment firstFragment = new FirstFragment();
        if (findViewById(R.id.contentFrame) != null) {
            // Found the ID of only one Fragment ==> Portrait mode
            // Remove the existing fragment before add new one
            if (savedInstanceState != null) {
                getSupportFragmentManager().executePendingTransactions();
                Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.contentFrame);
                if (fragmentById != null) {
                    getSupportFragmentManager().beginTransaction().remove(fragmentById).commit();
                }
            }

            // Add new one
            getSupportFragmentManager().beginTransaction().add(R.id.contentFrame, firstFragment).commit();
        } else {
            // Landscape mode
            // Remove the existing fragments before add new one
            if (savedInstanceState != null) {
                getSupportFragmentManager().executePendingTransactions();
                Fragment firstFragmentById = getSupportFragmentManager().findFragmentById(R.id.firstFrame);
                if (firstFragmentById != null) {
                    getSupportFragmentManager().beginTransaction().remove(firstFragmentById).commit();
                }
                Fragment secondFragmentById = getSupportFragmentManager().findFragmentById(R.id.secondFrame);
                if (secondFragmentById != null) {
                    getSupportFragmentManager().beginTransaction().remove(secondFragmentById).commit();
                }
            }

            // Add new one
            getSupportFragmentManager().beginTransaction().add(R.id.firstFrame, firstFragment).commit();
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        drawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        drawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_actions, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(drawerToggle.onOptionsItemSelected(item)) {
            return true;
        }

        switch (item.getItemId()) {
            case R.id.search:
                Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show();
                return true;
            case R.id.about:
                Intent intent = new Intent(this, ContactActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_ABOUT);
                intent.putExtras(bundle);
                startActivity(intent);
                return true;
            case R.id.help:
                intent = new Intent(this, ContactActivity.class);
                bundle = new Bundle();
                bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_HELP);
                intent.putExtras(bundle);
                startActivity(intent);
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemPressed(String content) {
        SecondFragment secondFragment = SecondFragment.newInstance(content);
        if (findViewById(R.id.contentFrame) != null) {
            // Found the ID of only one Fragment ==> Portrait mode
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.contentFrame, secondFragment);
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        } else {
            // Landscape mode
            getSupportFragmentManager().beginTransaction().replace(R.id.secondFrame, secondFragment).commit();
        }
    }
}

Khi này nếu thực thi chương trình, bạn hãy thử nghiệm sự thông minh của ứng dụng khi xoay màn hình thiết bị ngang dọc nhé.

Chúng ta vừa xem qua các kiến thức về việc hiển thị các Fragment lên cùng một Activity theo dạng tĩnh và động. Chúng ta vẫn sẽ cần phải nói nhiều hơn về Fragment ở bài học sau.

Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:

Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
Comment bên dưới mỗi bài nếu có thắc mắc.
Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.

Bài Kế Tiếp

Bài sau chúng ta sẽ nói về vòng đời của Fragment, và chính thức mang Fragment lên TourNote nhé.

Advertisements
Rating: 5.0/5. From 8 votes.
Please wait...

2 comments

  1. mình học tới bài 32 rồi bạn dạy rất hay, mong rằng thời gian tới bạn sẽ làm 1 tập sách như kiểu sách Java bạn đã làm, thân ái !

    1. Cảm ơn bạn, nhất định mình sẽ dành thời gian tạo hết các ebook cho các bài viết của mình, bạn đón xem nhé 😉

Gửi phản hồi