Bài 5: Fragment - Lập trình Android cơ bản

Đăng bởi: Admin | Lượt xem: 8218 | Chuyên mục: Android


1. Tổng quan về Fragment, một số khái niệm cơ bản

  1. Trong các ứng dụng android, tại một thời điểm, chỉ có một Activity được hiển thị duy nhất trên màn hình. Chúng ta muốn chia màn hình ra nhiều phần để dễ sử dụng thì fragment đáp ứng điều đó
  2. Fragment là một thành phần android độc lập, được sử dụng bởi một activity, giống như một sub-activity. Fragment có vòng đời và giao diện riêng. Các Fragment thường có một file java đi kèm với file giao diện xml. Các fragment không có file giao diện xml thường được gọi là headless fragments.
  3. Fragment sử dụng phương thức getActivity() để lấy ra Activity cha
  4. Fragment được định nghĩa trong file xml của activity (static definition) hoặc có thể sửa đổi fragment khi đang chạy (dynamic definition)
1.1 Vòng đời của một fragment
onAttach(): hàm này thực hiện tạo tham chiếu từ một fragment đến activity đã khởi tạo nó, và thực hiện một số bước trong quá trình khởi tạo
onCreate(): thực hiện khởi tạo fragment
onCreateView(): thực hiện tạo giao diện(view), trả về view là giao diện file xml tương ứng fragment. ko nên tương tác với activity trong hàm này bởi vì activity chưa được khởi tạo đầy đủ. Không cần thực hiện hàm này với các fragment không có header
onActivityCreated(): thực hiện hoàn thành nốt việc khởi tạo activity và fragment. Trong bước này chúng ta có thể gọi findViewById()
onStart(): thực hiện việc hiển thị fragment lên màn hình
onResume(): fragment chính thức hoạt động hoàn toàn
onPause(): fragment bị tạm dừng hoạt động, nó vẫn có thể được nhìn thấy
onStop(): fragment bị ẩn
onDestroyView(): giao diện(view) của fragment bị hủy. Nếu nó được gọi quay lại, nó sẽ quay trở lại thực hiện hàm onCreateView()
onDestroy(): bị hủy
onDetach(): bị hủy hoàn toàn
1.2  Static Fragment
Static Fragment là kiểu fragment được khai báo (định nghĩa) trực tiếp trong file activity_main.xml
Ví dụ ta có 2 static fragment là MyFragment1.java(fragment1.xml) và MyFragment2.java(fragment2.xml)
Trong file activity_main.xml khai báo tĩnh
<fragment
    android:id="@+id/listFragment"
    android:layout_weight="1"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    class="com.example.android.fragment3.MyFragment1"
    tools:layout="@layout/fragment1">
</fragment>
Thuộc tính class chỉ đến đường dẫn chứa file java tương ứng, thuộc tính layout_weight chỉ ra rằng fragment này chiếm một không gian bao nhiêu phần trong activity_main.xml
Chú ý: Bắt buộc phải có thuộc tính android:id. Nếu không sẽ gây lỗi.
Hai class MyFragment1.java và MyFragment2.java phải extends FragmentVà phải ghi đè phương thức onCreateView
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    View view = inflater.inflate(R.layout.fragment1, container, false);
    return view;
}

Ví dụ : 

MainActivity.java
package com.vncoder.fragment;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
MyFragment1.java
package com.vncoder.fragment;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class fragment1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater,container,savedInstanceState);
        View view = inflater.inflate(R.layout.activity_fragment1,container,false);
        return view;
    }
}
MyFragment2.java
package com.vncoder.fragment;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class fragment2 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater,container,savedInstanceState);
        View view = inflater.inflate(R.layout.activity_fragment2,container,false);
        return view;
    }
}
fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".fragment1">

    <TextView
        android:background="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="fragment 1"
        android:textSize="30dp"
        android:layout_gravity="center"
        />
</LinearLayout>
fragment2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".fragment2">

    <TextView
        android:background="@color/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="fragment 2"
        android:textSize="30dp"
        android:layout_gravity="center"
        />
</LinearLayout>
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/listFragment"
        class="com.vncoder.fragment.fragment1"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        tools:layout="@layout/activity_fragment1">

    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        class="com.vncoder.fragment.fragment2"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        tools:layout="@layout/activity_fragment2">

    </fragment>

</LinearLayout>

Kết quả : 

2. Quản lý Fragment

2.1 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.
Có hai lớp FragmentManager mà bạn có thể cân nhắc sử dụng.
  1. FragmentManager ở gói android.app – Được gọi thông qua phương thức *getFragmentManager() *của Activity sử dụng khi project của bạn khai báo minSdkVersion lớn hơn 11.
  2. 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 đó là.
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 . Hoặc ID của Layout chứa Fragment như ví dụ tạo Fragment động như trên. 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.
2.2 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ể gọi FragmentTransaction thông qua phương thức beginTransaction() từ FragmentManager.
FragmentTransaction có các phương thức thú vị để bạn quản lý các Fragment như sau.
  1. 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.
  2. 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.
  3. remove() – Khi bạn muốn gỡ bỏ Fragment ra khỏi một FrameLayout nào đó.
  4. 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 Layout ở 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. 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.
  5. 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 đó.
Ví dụ sử dụng FragmentManager và Fragment Transaction:
FragmentManager fm = getFragmentManager();

// add
FragmentTransaction ft_add = fm.beginTransaction();
ft_add.add(R.id.your_placehodler,new YourFragment());
ft_add.commit();

// replace
FragmentTransaction ft_rep = fm.beginTransaction();
ft_rep.replace(R.id.your_placehodler, new YourFragment());
ft_rep.commit();

// remove
Fragment fragment = fm.findFragmentById(R.id.your_placehodler);
FragmentTransaction ft_remo = fm.beginTransaction();
ft_remo.remove(fragment);
ft_remo.commit();

3. Truyền dữ liệu giữa các Fragment

Có nhiều cách để truyền dữ liệu qua lại giữa hai đối tượng fragment, trong đây mình sẽ giới thiệu cách đơn giản mình vẫn hay sử dụng nhất: FragmentA -> Activity -> FragmentB. Có nhiều bạn sẽ thắc mắc tại sao lại không truyền trực tiếp từ fragmentA -> fragmentB mà lại truyền lòng vòng như thế? Bởi vì đối với fragment bạn không chắc nó luôn luôn được khởi tạo. Vì vậy để chắc chắn fragmentB đã được khởi tạo thì chúng ta phải thông qua activity. Tránh giao tiếp trực tiếp giữa hai fragment.
Để hiểu rõ hơn về cách truyền dữ liệu bằng interface chúng ta cùng đi vào ví dụ :
Ứng dụng có 2 Fragment xanh và trắng, fragment màu xanh ở trên sau khi nhập dữ liệu xong, bấm nút send thì fragment ở dưới sẽ nhận được dữ liệu và hiển thị.
Tổng quan project:
Tạo interface DemoFragmentInterface.java
package com.vncoder.fragment;

public interface DemoFragmentInterface {
    void sendData(String name, String age);
}
Hàm MainActivity.java
Ở hàm main ta khai báo 2 fragment  và implements class DemoFragmentInterface và triển khai hàm sendData để gửi dữ liệu.
Hàm showInfor ta sẽ định nghĩa ở trong Fragment2.
package com.vncoder.fragment;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;


import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements DemoFragmentInterface {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        
        Fragment1 fragment1 = new Fragment1();
        transaction.add(R.id.containerTop,fragment1);
        
        Fragment2 fragment2 = new Fragment2();
        transaction.add(R.id.containerBottom,fragment2);
        transaction.commit();
    }
    @Override
    public void sendData(String name, String age) {
        Fragment2 fragment2 = (Fragment2) getSupportFragmentManager().findFragmentById(R.id.containerBottom);
        if (fragment2 != null || fragment2.isInLayout()){
            fragment2.showInfor(name,age);
        }else {
            Toast.makeText(getApplicationContext(),"???",Toast.LENGTH_LONG).show();
        }
    }
}
Fragment1.java
Ở đây chúng ta khai báo một biến interface dùng để lắng nghe thay đổi từ Fragment ( Khi người dùng nhập đầy đủ và click button send ), xét sự kiện onClick khi click vào nút SEND.
Override lại hàm onAttach để kiểm tra xem cái Activity hiện tại đã implement cái interface kia chưa. Tại đây ta tiến hành gán sự kiện cho biến interface đó.
package com.vncoder.fragment;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class Fragment1 extends Fragment implements View.OnClickListener{
    private EditText edtName, edtAge;
    private Button btnSend;
    private DemoFragmentInterface listener;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity)
            this.listener = (DemoFragmentInterface) context;
        else
            throw new RuntimeException(context.toString() + "must implement onViewSelected!");
    }
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater,container,savedInstanceState);
        View view = inflater.inflate(R.layout.activity_fragment1,container,false);
        edtName = view.findViewById(R.id.edtName);
        edtAge = view.findViewById(R.id.edtAge);
        btnSend = view.findViewById(R.id.btnSend);
        btnSend.setOnClickListener(this);
        return view;
    }
    
    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.btnSend:
                String name = edtName.getText().toString().trim();
                String age = edtAge.getText().toString().trim();

                if (!name.equals("") && !age.equals("")) {
                    listener.sendData(name, age);
                } else if (name.equals("")) {
                    edtName.requestFocus();
                } else {
                    edtAge.requestFocus();
                }
                break;
        }
    }
}
Fragment2.java
Ở fragment này chúng ta chỉ cần khai báo phương thức showInfor() hiển thị dữ liệu cho nó.
package com.vncoder.fragment;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class Fragment2 extends Fragment {
    private TextView tvName, tvAge;
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater,container,savedInstanceState);
        View view = inflater.inflate(R.layout.activity_fragment2,container,false);
        tvName = (TextView) view.findViewById(R.id.tvName);
        tvAge = (TextView) view.findViewById(R.id.tvAge);
        return view;
    }
    public void showInfor(String name, String age){
        tvName.setText(name);
        tvAge.setText(age);
    }
}
activity_fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
     android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    tools:context=".Fragment1">


        <LinearLayout
            android:gravity="center"
            android:layout_gravity="center"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <EditText
                android:textSize="30dp"
                android:id="@+id/edtName"
                android:hint="Name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <EditText
                android:textSize="30dp"
                android:id="@+id/edtAge"
                android:hint="Age"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <Button
                android:id="@+id/btnSend"
                android:text="SEND"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        </LinearLayout>



  
</FrameLayout>
activity_fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
     android:orientation="vertical"
     tools:context=".Fragment2">


        <LinearLayout
            android:gravity="center"
             android:layout_gravity="center"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                 android:id="@+id/tvName"
                android:textSize="30dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                 android:id="@+id/tvAge"
                android:textSize="30dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </LinearLayout>

</FrameLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
     android:weightSum="2"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment

        android:id="@+id/containerTop"
        class="com.vncoder.fragment.Fragment1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        tools:layout="@layout/activity_fragment1">

    </fragment>

    <fragment
        android:id="@+id/containerBottom"
        class="com.vncoder.fragment.Fragment2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        tools:layout="@layout/activity_fragment2">

    </fragment>

</LinearLayout>
Kết quả:
Bài tiếp theo: Các thành phần giao diện cơ bản trong Android >>
vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!