Khóa học lập trình Android cơ bản

Serial tutorial hướng dẫn lập trình Android cơ bản

Fragment và cơ chế BackStack Fragment và cơ chế BackStack Fragment và cơ chế BackStack Fragment và cơ chế BackStack Fragment và cơ chế BackStack 4.5/5 (68 reviews)

Fragment và cơ chế BackStack

Đã đăng 2016-09-23 09:30:50 bởi Kteam
1 bình luận 16344 lượt xem
Fragment và cơ chế BackStack 4.5 /5 stars (4 reviews)
 

Dẫn nhập

Ở bài học trước chúng ta đã làm quen với ACTIVITY & VÒNG ĐỜI CỦA ỨNG DỤNG

Với việc càng ngày càng nhiều mẫu mã thiết bị Android ra đời thì người ta đã nghĩ ra những cách khác nhau để bố trí giao diện ứng dụng sao cho phù hợp để tận dụng diện tích màn hình. Một trong số đó là Fragment. Bài viết này chúng ta sẽ cùng tìm hiểu về nó.


Nội dung

Để đọc hiểu bài này tốt nhất các bạn nên có kiến thức cơ bản về các phần:

Trong bài học này, chúng ta sẽ cùng tìm hiểu các vấn đề:

  • Fragment là gì?
  • Vòng đời của Fragment.
  • Cách tạo một Activity với Fragment cơ bản.
  • Tích hợp Backstack cho Fragment trong Activity đã tạo.

Fragment

Fragment là gì?

Trước tiên chúng ta xem 2 hình phía dưới.      

    

OK, bạn nhận xét thế nào?

Rõ ràng ở hình thứ 2, cách phân bố các item được tận dụng tốt hơn. Mỗi mục bên trái tương ứng với một nội dung bên phải. So với việc chỉ hiển thị đầu mục Setting như hình thứ nhất.

Đó cũng là ưu điểm khi sử dụng Fragment!

Mặt khác, chúng ta có thể hiểu: Fragment là một dạng nhiều Activity con, lồng trong Activity mẹ. Các Fragment cũng sở hữu vòng đời (Lifecycle) của mình, vòng đời của Fragment cũng gần gần tương tự như Activity (giống đến 89%).


Tạo một Fragment

Các bạn có thể tưởng tượng rằng một Fragment là một vùng chứa trong Activity, một module riêng. Và nó có vòng đời của mình.

Bài viết này chủ yếu là thực hành, và chúng ta sẽ làm một ứng dụng nhỏ có tên FragmentExample, có sử dụng Fragment trong thư viện Android Support Library (hỗ trợ đến Android đời cũ tận 1.6).

Bước 1: Chúng ta vẫn tạo một project bình thường như mọi khi, với Activity là Empty Activity:

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/activity_main"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    tools:context="com.howkteam.fragmentexample.MainActivity">



    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World!"/>

</RelativeLayout>

Bước 2: Thêm Fragment vào XML

Mặc dù Fragment có tính module hóa cao, có thể được tái sử dụng. Nhưng mỗi Fragment muốn sử dụng đều phải gắn với một Activity mẹ. Do đó, chúng ta sẽ thêm vào trong Activity chính như sau, đồng thời chuyển RelativeLayout sang dạng LinearLayout cho dễ hình dung:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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"
    tools:context="com.howkteam.fragmentexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

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

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

</LinearLayout>

Cùng nhìn lại thiết kế của XML, chúng ta sẽ thấy fragment đã được tạm dựng (“tạm” nhé, vì chưa có nội dung, nhưng đã thấy được chiều cao của nó):

Bước 3: Tạo các fragment tương ứng:

Như các bạn đã thấy, ở trên chúng ta đã định nghĩa 2 fragment trong Activity. Fragment thực chất là 1 dạng Activity con, vì vậy chúng ta cần dựng một class riêng cho nó để dễ quản lý.

Chúng ta sẽ tạo một class tên là Fragment1 và kế thừa từ lớp android.support.v4.app.Fragment:

  • Đặt tên class là Fragment1.java và code sẽ có nội dung như sau:
package com.howkteam.fragmentexample;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}
  • Tiếp đến là Fragment2.java cũng tương tự:
package com.howkteam.fragmentexample;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment2 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

Khoan! Tại sao lại là onCreateView mà không phải là onCreate?

Như đã đề cập nhiều lần ở trên, Fragment không phải Activity mà chứa trong Activity. Nó có vòng đời riêng, layout riêng và vì thế sẽ cần một số cơ chế khác để đổ dữ liệu và các View vào bên trong. Hãy nhìn qua sơ đồ vòng đời của Fragment:

Đấy, các thao tác rẽ nhánh giờ đây đã được chuyển sang 2 bước mới là onCreateViewonDestroyView.

Bước 4: Tạo file layout cho Fragment.

Một Fragment cũng có layout như Activity mà thôi, và vì thế chúng ta cần tạo layout cho nó. Chuột phải vào thư mục res/layout > New > Layout resource file:

  • Chúng ta đặt tên cho layout là fragment_1, trong Fragment này ta sẽ đặt một TextView với nội dung tùy ý, đại khái code XML có nội dung như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
   
    <TextView
        android:text="Hello Kteam Fragment 1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>
  • Tương tự, chúng ta tạo thêm một layout nữa có tên fragment_2:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF4081"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Kteam Fragment 2"/>

</LinearLayout>

Bước 5: Liên kết class java với layout fragment:

Class java đã có, XML cũng đã có, giờ chúng ta bắt đầu liên kết fragment với layout tương ứng. Nếu như trong Activity, chúng ta sử dụng setContentView thì ở Fragment, chúng ta dùng tham số đầu vào Inflater để nhét layout, việc thực hiện cũng khác:

Fragment_1.java

package com.howkteam.fragmentexample;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_1, container, false);
    }
}

Fragment_2.java

package com.howkteam.fragmentexample;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment2 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_2, container, false);
    }
}

Bước 6: Sau khi chuẩn bị xong xuôi. Activity đã có, 2 Fragment cũng đã có. Giờ chúng ta sẽ liên kết chúng lại. Rất đơn giản, chỉ việc sửa đổi một chút ở file activity_main.xml như sau:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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:weightSum="3"
    tools:context="com.howkteam.fragmentexample.MainActivity">

    <fragment
        android:id="@+id/fragment_1"
        android:name="com.howkteam.fragmentexample.Fragment1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <fragment
        android:id="@+id/fragment_2"
        android:name="com.howkteam.fragmentexample.Fragment2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2"/>

</LinearLayout>

Việc khai báo rất đơn giản, phải không nào? Chạy thử thôi:


Cơ chế Backstack

Với những giải thích và ví dụ ở phần trên, chúng ta đã biết cách để gán 2 Fragment vào một Activity. Việc gán nhiều Fragment vào Activity cũng vậy.

Vậy giả sử trong trường hợp bạn chỉ có một khung Fragment, và muốn hoán đổi nhiều Fragment khác lần lượt vào cái khung đó thì sao? Và việc gì xảy ra nếu chúng ta nhấn nút Back (quay lại) trên máy?

Các bạn hãy nhìn hình mô tả sau:

Chúng ta đều mong muốn cách mọi thứ diễn ra sẽ như trên. Khi chuyển fragment và nhấn back thì fragment cũ sẽ được đưa về. Nhưng thực tế sẽ là…

Tại sao? Vì nút Back trên thiết bị Android mặc định sẽ chỉ có tác dụng với Activity đang hiện hữu chứ không phải Fragment. Và vì thế chúng ta sẽ cần sử dụng Fragment Backstack: Đưa các Fragment vào một ngăn xếp (giống như khi bạn duyệt trang web vậy, nhấn back thì trình duyệt sẽ quay lại trang trước đó).

Bước 1: Cách thực hiện cũng không khó. Vẫn sử dụng project mà chúng ta đang code, thêm vào một Activity khác tạm gọi là BackstackActivity:

BackstackActivity.java

package com.howkteam.fragmentexample;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

public class BackstackActivity extends AppCompatActivity {

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

        setContentView(R.layout.activity_backstack);
    }
}
  • Và cùng với đó là một file layout tương ứng:

activity_backstack.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">



    <Button

        android:id="@+id/btn_replace_fragment"

        android:text="Click me"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"/>



    <FrameLayout

        android:id="@+id/container_body"

        android:layout_width="match_parent"

        android:layout_height="match_parent"/>



</LinearLayout>

Các bạn chú ý tag FrameLayout ở trên, đây sẽ là cái khung mà lát nữa chúng ta sẽ đổ fragment vào. Được ư? Quá được chứ, Fragment có thể được đổ vào layout bất kỳ. Nút Click me sẽ là nút mà chúng ta sử dụng để chuyển Fragment trong ví dụ này.

Bước 2: Đây mới là phần hay.

Để hiểu Fragment Backstack thì đương nhiên là chúng ta cần… Fragment. Sau khi làm ví dụ ở phần trên thì lúc này chúng ta đã có 2 cái là Fragment_1Fragment_2, và 2 Fragment này hoàn toàn có thể được sử dụng lại.

Và Android cung cấp một công cụ cực kỳ tuyệt vời để quản lý Fragment. Đó là class FragmentManager. Nó sẽ giúp các bạn thay đổi / thêm / bỏ Fragment tùy ý. Để xem chúng có gì khác nhau, chúng ta sẽ viết từng hàm riêng lẻ, sau đó gọi trong onCreate để biết tính năng của từng cái:

Kiểu 1: Fragment_2 thay thế hoàn toàn Fragment_1, Fragment_1 bị hủy. Đơn giản là chỉ cần Lấy Fragment_2 chiếm vị trí của Fragment_1 mà thôi:

  • Thêm 2 dòng sau vào fragment_1.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:textAlignment="viewEnd"
        android:layout_gravity="end"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Kteam Fragment 1"/>

</LinearLayout>
  • Để cho chữ sang bên phải, lát nữa chúng ta sẽ biết tác dụng ^^
    • Sửa đổi file AndroidManifest.xml để chỉ định BackstackActivity là Activity khởi chạy:
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

          package="com.howkteam.fragmentexample">



    <application

        android:allowBackup="true"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:supportsRtl="true"

        android:theme="@style/AppTheme">

        <activity android:name=".BackstackActivity">

            <intent-filter>

                <action android:name="android.intent.action.MAIN"/>



                <category android:name="android.intent.category.LAUNCHER"/>

            </intent-filter>

        </activity>

    </application>



</manifest>

Bước 3: Cuối cùng sửa file BackstackActivity như sau. Hơi dài chút nhưng các bạn chú ý mấy chỗ tô vàng tô xanh là đủ:

package com.howkteam.fragmentexample;



import android.os.Bundle;

import android.support.annotation.Nullable;

import android.support.v4.app.Fragment;

import android.support.v4.app.FragmentManager;

import android.support.v4.app.FragmentTransaction;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

import android.widget.Button;



public class BackstackActivity extends AppCompatActivity {



    private Button btnFrag;



    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        setContentView(R.layout.activity_backstack);

        btnFrag = (Button) findViewById(R.id.btn_replace_fragment);

        btnFrag.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                replaceFragmentContent(new Fragment2());

                Log.e("Replaced fragment", "2");

            }

        });



        initFragment();

    }



    private void initFragment() {

        Fragment1 firstFragment = new Fragment1();

        FragmentManager fragmentManager = getSupportFragmentManager();

        FragmentTransaction ft = fragmentManager.beginTransaction();

        ft.replace(R.id.container_body, firstFragment);

        ft.commit();

    }



    protected void replaceFragmentContent(Fragment fragment) {

        if (fragment != null) {

            FragmentManager fmgr = getSupportFragmentManager();

            FragmentTransaction ft = fmgr.beginTransaction();

            ft.replace(R.id.container_body, fragment);

            ft.commit();

        }

    }

}
  • Ta khởi tạo và nhét Fragment_1 vào Activity ngay lúc Activity hiện hình bằng cách gọi hàm initFragment() trong onCreate().
  • Mình viết hàm replaceFragmentContent để thay thế cái container_body (vốn chính là cái FrameLayout trong activity_backstack.xml đó. Sau đó đặt event cho cái nút: Khi bấm nút thì Fragment_2 sẽ nhảy vào.

Tức là khi mở lên, chưa bấm nút, nó sẽ thế này:

Và sau khi bấm nút, nó sẽ thế này:

Mình cố tình để hiện Android Monitor để các bạn có thể xem log đó. Nhớ tận dụng log để test nhé.

Ấy thế nhưng khi chúng ta nhấn nút Back thì…

Bùm! Activity đã bị hủy. Đó là vì nút back chỉ tác dụng lên Activity, và không có Fragment nào khác trong danh sách Backstack (giống việc bàn cờ có 2 quân, quân này ăn quân kia thì chỉ còn 1).

Và để làm cho nút Back sau khi nhấn sẽ quay lại về Fragment_1 thì chúng ta viết 1 hàm khác và gọi ra trong Activity như sau:

package com.howkteam.fragmentexample;



import android.os.Bundle;

import android.support.annotation.Nullable;

import android.support.v4.app.Fragment;

import android.support.v4.app.FragmentManager;

import android.support.v4.app.FragmentTransaction;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

import android.widget.Button;



public class BackstackActivity extends AppCompatActivity {



    private Button btnFrag;



    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        setContentView(R.layout.activity_backstack);

        btnFrag = (Button) findViewById(R.id.btn_replace_fragment);

        btnFrag.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                addFragment(new Fragment2());

                Log.e("Replaced fragment", "2");

            }

        });



        initFragment();

    }



    private void initFragment() {

        Fragment1 firstFragment = new Fragment1();

        FragmentManager fragmentManager = getSupportFragmentManager();

        FragmentTransaction ft = fragmentManager.beginTransaction();

        ft.replace(R.id.container_body, firstFragment);

        ft.commit();

    }



    protected void replaceFragmentContent(Fragment fragment) {

        if (fragment != null) {

            FragmentManager fmgr = getSupportFragmentManager();

            FragmentTransaction ft = fmgr.beginTransaction();

            ft.replace(R.id.container_body, fragment);

            ft.commit();

        }

    }



    protected void addFragment(Fragment fragment) {

        FragmentManager fmgr = getSupportFragmentManager();

        FragmentTransaction ft = fmgr.beginTransaction();

        ft.add(R.id.container_body, fragment);

        ft.addToBackStack(fragment.getClass().getSimpleName());

        ft.commit();

    }

}

Chạy thử nào, khi chúng ta nhấn nút thì màn hình sẽ chuyển sang Fragment_2:

Và khi nhấn Back thì…

Không có gì khó hiểu, phải không nào?


Kết luận

Qua bài này chúng ta đã nắm được Fragment là gì, mối quan hệ của nó với Activity, và cách tạo một Fragment đơn giản. Và cơ chế Backstack.

Chúng ta sẽ tìm hiểu về kiểu thiết kế Material Design của Google, cũng như quy chuẩn code Android (Coding Convention) hiệu quả qua bài tiếp theo: MATERIAL DESIGN & CODING CONVENTION

Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.


Tài liệu 

Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học Fragment và cơ chế BackStack dưới dạng file PDF trong link bên dưới.

Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com

Đừng quên like hoặc +1 Google để ủng hộ Kteam và tác giả nhé! 


Thảo luận

Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần BÌNH LUẬN bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng. 

 

Chia sẻ:
Thảo luận Hỏi và đáp Báo lỗi bài viết
Hủy bỏ   hoặc  
Fragment và cơ chế BackStack
vuhuutuan0 2017-10-29 13:36:13

Khi sử dụng backstack ở trên fracment 2 thì nó vẫn tác động lên các phần tử cả fracment 1 nằm ở phía sau thì đây là do lỗi gì hay là nó vốn như thế. nếu là lỗi thì fix như thế nào để khi đứng trên fracment 2 o thẻ tác dộng lên các pahanf tử frament 1 được nữa như là 1 activity vậy

0 bình chọn
Reply
Hủy bỏ   hoặc  
Hủy bỏ   hoặc  

Chiến dịch

Kteam - Howkteam Free Education