Android元件之Fragment(一)---基礎知識與運用

語言: CN / TW / HK

開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第20天

一、Fragment是什麼?為什麼要引入它?

是什麼?

Fragment是Android3.0後引入的一個新的API,他出現的初衷是為了適應大螢幕的平板電腦, 當然現在他仍然是平板APP UI設計的寵兒,而且我們普通手機開發也會加入這個Fragment, 我們可以把他看成一個小型的Activity,又稱Activity片段!想想,如果一個很大的介面,我們 就一個佈局,寫起介面來會有多麻煩,而且如果元件多的話是管理起來也很麻煩!而使用Fragment 我們可以把螢幕劃分成幾塊,然後進行分組,進行一個模組化的管理!從而可以更加方便的在 執行過程中動態地更新Activity的使用者介面!另外Fragment並不能單獨使用,他需要巢狀在Activity 中使用,儘管他擁有自己的生命週期,但是還是會受到宿主Activity的生命週期的影響,比如Activity 被destory銷燬了,他也會跟著銷燬!

為什麼引入它?

在這裡插入圖片描述 引用官方的一張圖片,其實已經說明問題了,就是為了更好的適配大屏,在大屏的時候,不需要去在一個activity內部通過複雜的佈局和介面去實現,只需要去在一個activity內部,通過多個fragment來做介面佈局實現即可,而且針對於多個fragment來說, 每個fragment有單獨的生命週期,

二、使用方法

Demo樣例,我們在一個介面中,有上下兩個fragment,如圖所示: 在這裡插入圖片描述

1.動態載入

程式碼如下(示例): Step 1: activity佈局檔案,兩個FrameLayout task_test.xml ```java

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

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

**Step 2: Fragment建立,檢視載入,資料賦值** `BlankFragment .java`java package com.itbird.fragment;

import android.content.Context; import android.os.Bundle;

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

import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;

import com.itbird.R;

/* * A simple {@link Fragment} subclass. * Use the {@link BlankFragment#newInstance} factory method to * create an instance of this fragment. / public class BlankFragment extends Fragment {

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private String TAG = BlankFragment.class.getSimpleName();

public BlankFragment() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment BlankFragment.
 */
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
    BlankFragment fragment = new BlankFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

@Override
public void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
}

@Override
public void onStop() {
    Log.d(TAG, "onStop");

    super.onStop();
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    Log.d(TAG, "onViewCreated");
    super.onViewCreated(view, savedInstanceState);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    Log.d(TAG, "onActivityCreated");
    super.onActivityCreated(savedInstanceState);
}

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
}

@Override
public void onDestroy() {
    Log.d(TAG, "onDestroy");

    super.onDestroy();
}

@Override
public void onDestroyView() {
    Log.d(TAG, "onDestroyView");

    super.onDestroyView();
}

@Override
public void onDetach() {
    Log.d(TAG, "onDetach");

    super.onDetach();
}

@Override
public void onAttach(@NonNull Context context) {
    Log.d(TAG, "onAttach");

    super.onAttach(context);
}

@Override
public void onHiddenChanged(boolean hidden) {
    Log.d(TAG, "onHiddenChanged");

    super.onHiddenChanged(hidden);
}

@Override
public void onResume() {
    Log.d(TAG, "onResume");

    super.onResume();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    Log.d(TAG, "onCreateView");
    View view = inflater.inflate(R.layout.fragment_blank, container, false);
    textView = view.findViewById(R.id.textview);
    textView.setText(mParam1 + "       " + mParam2);
    return view;
}

TextView textView;

} **Step 3: Activity在onCreate( )方法中呼叫setContentView()之後呼叫FragmentTransaction 進行事務提交** `FragmentTestActivity.java `java package com.itbird.fragment;

import android.os.Bundle; import android.util.Log;

import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentTransaction;

import com.itbird.R;

public class FragmentTestActivity extends FragmentActivity {

private static final String TAG = FragmentTestActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.task_test);
    Log.e(TAG, TAG + " onCreate");
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.add(R.id.fragment1, BlankFragment.newInstance("name1", "age1"), "top");
    transaction.add(R.id.fragment2, BlankFragment.newInstance("name2", "age2"), "bootom");
    transaction.commit();
}

@Override
public void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
}

@Override
public void onStop() {
    Log.d(TAG, "onStop");
    super.onStop();
}

@Override
public void onResume() {
    Log.d(TAG, "onResume");
    super.onResume();
}

@Override
protected void onPause() {
    Log.d(TAG, "onPause");
    super.onPause();
}

@Override
protected void onDestroy() {
    Log.e(TAG, TAG + " onDestroy");
    super.onDestroy();
}

} ```

2.靜態載入

在xml中宣告兩個fragment,指定為具體的fragment

Step 1:定義Fragment的佈局,就是fragment顯示內容的

```java

<TextView
    android:id="@+id/textview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="@string/hello_blank_fragment" />

```

Step 2:自定義一個Fragment類,需要繼承Fragment或者他的子類,重寫onCreateView()方法 在該方法中呼叫:inflater.inflate()方法載入Fragment的佈局檔案,接著返回載入的view物件 BlankFragment.java

```java package com.itbird.fragment;

import android.content.Context; import android.os.Bundle;

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

import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;

import com.itbird.R;

/* * A simple {@link Fragment} subclass. * Use the {@link BlankFragment#newInstance} factory method to * create an instance of this fragment. / public class BlankFragment extends Fragment {

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private String TAG = BlankFragment.class.getSimpleName();

public BlankFragment() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment BlankFragment.
 */
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
    BlankFragment fragment = new BlankFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

@Override
public void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
}

@Override
public void onStop() {
    Log.d(TAG, "onStop");

    super.onStop();
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    Log.d(TAG, "onViewCreated");
    super.onViewCreated(view, savedInstanceState);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    Log.d(TAG, "onActivityCreated");
    super.onActivityCreated(savedInstanceState);
}

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
}

@Override
public void onDestroy() {
    Log.d(TAG, "onDestroy");

    super.onDestroy();
}

@Override
public void onDestroyView() {
    Log.d(TAG, "onDestroyView");

    super.onDestroyView();
}

@Override
public void onDetach() {
    Log.d(TAG, "onDetach");

    super.onDetach();
}

@Override
public void onAttach(@NonNull Context context) {
    Log.d(TAG, "onAttach");

    super.onAttach(context);
}

@Override
public void onHiddenChanged(boolean hidden) {
    Log.d(TAG, "onHiddenChanged");

    super.onHiddenChanged(hidden);
}

@Override
public void onResume() {
    Log.d(TAG, "onResume");

    super.onResume();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    Log.d(TAG, "onCreateView");
    View view = inflater.inflate(R.layout.fragment_blank, container, false);
    textView = view.findViewById(R.id.textview);
    textView.setText(mParam1 + "       " + mParam2);
    return view;
}

TextView textView;

} ``` Step 3:在需要載入Fragment的Activity對應的佈局檔案中新增fragment的標籤, 記住,name屬性是全限定類名哦,就是要包含Fragment的包名,如:

```java

<androidx.fragment.app.FragmentContainerView
    android:name="com.itbird.fragment.BlankFragment"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

<androidx.fragment.app.FragmentContainerView
    android:name="com.itbird.fragment.BlankFragment"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

``` Step 4: Activity在onCreate( )方法中呼叫setContentView()載入佈局檔案即可!

```java package com.itbird.fragment;

import android.os.Bundle; import android.util.Log;

import com.itbird.R;

public class FragmentTestActivity extends FragmentActivity {

private static final String TAG = FragmentTestActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.task_test);
    Log.e(TAG, TAG + " onCreate");
}

} ```

三、生命週期

在這裡插入圖片描述

幾種情況下,fragment生命週期: ①Activity載入Fragment的時候,依次呼叫下面的方法: onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart ->onResume ②當我們弄出一個懸浮的對話方塊風格的Activity,或者其他,就是讓Fragment所在的Activity可見,但不獲得焦點 onPause ③當對話方塊關閉,Activity又獲得了焦點: onResume ④當我們替換Fragment,並呼叫addToBackStack()將他新增到Back棧中 onPause -> onStop -> onDestoryView !!注意,此時的Fragment還沒有被銷燬哦!!! ⑤當我們按下鍵盤的回退鍵,Fragment會再次顯示出來: onCreateView -> onActivityCreated -> onStart -> onResume ⑥如果我們替換後,在事務commit之前沒有呼叫addToBackStack()方法將 Fragment新增到back棧中的話;又或者退出了Activity的話,那麼Fragment將會被完全結束, Fragment會進入銷燬狀態 onPause -> onStop -> onDestoryView -> onDestory -> onDetach

四、主要方法

針對在一個Activity中的某個Layout中切換Fragment,,無非兩種方法:

1)replace

我們自己看一下方法註釋 ```java

/**
 * Replace an existing fragment that was added to a container.  This is
 * essentially the same as calling {@link #remove(Fragment)} for all
 * currently added fragments that were added with the same containerViewId
 * and then {@link #add(int, Fragment, String)} with the same arguments
 * given here.
 *
 * @param containerViewId Identifier of the container whose fragment(s) are
 * to be replaced.
 * @param fragment The new fragment to place in the container.
 * @param tag Optional tag name for the fragment, to later retrieve the
 * fragment with {@link FragmentManager#findFragmentByTag(String)
 * FragmentManager.findFragmentByTag(String)}.
 *
 * @return Returns the same FragmentTransaction instance.
 */
@NonNull
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
        @Nullable String tag)  {
    if (containerViewId == 0) {
        throw new IllegalArgumentException("Must use non-zero containerViewId");
    }
    doAddOp(containerViewId, fragment, tag, OP_REPLACE);
    return this;
}

``` 原始碼方法註釋裡面說的很明白,這個方法會移除所有的fragment,然後添加當前的fragment。 這時分為兩種情況,一種是fragment已有並且在前臺展示,一種是未有或者在後臺,針對於前者,此時replace,生命週期不會發生變化,針對後者,生命週期會重新走

java 2022-03-18 15:51:53.408 1141-1141/com.itbird D/BlankFragment1: onAttach 2022-03-18 15:51:53.408 1141-1141/com.itbird D/BlankFragment1: onCreate 2022-03-18 15:51:53.411 1141-1141/com.itbird D/BlankFragment1: onCreateView 2022-03-18 15:51:53.414 1141-1141/com.itbird D/BlankFragment1: onViewCreated 2022-03-18 15:51:53.414 1141-1141/com.itbird D/BlankFragment1: onActivityCreated 2022-03-18 15:51:53.416 1141-1141/com.itbird D/BlankFragment1: onStart 2022-03-18 15:51:53.418 1141-1141/com.itbird D/BlankFragment2: onDestroyView 2022-03-18 15:51:53.419 1141-1141/com.itbird D/BlankFragment2: onDestroy 2022-03-18 15:51:53.419 1141-1141/com.itbird D/BlankFragment2: onDetach 2022-03-18 15:51:53.420 1141-1141/com.itbird D/BlankFragment1: onResume

2)add+hide+show

分為兩種情況,一種fragment已存在,一種未存在,針對於前者,生命週期無變化,但是會回撥onHiddenChanged方法,針對於後者,生命週期會建立一次。

```java 2022-03-18 16:02:30.520 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45) 2022-03-18 16:02:30.546 2410-2410/com.itbird D/BlankFragment1: onAttach 2022-03-18 16:02:30.547 2410-2410/com.itbird D/BlankFragment1: onCreate 2022-03-18 16:02:30.548 2410-2410/com.itbird D/BlankFragment1: onCreateView 2022-03-18 16:02:30.580 2410-2410/com.itbird D/BlankFragment1: onViewCreated 2022-03-18 16:02:30.582 2410-2410/com.itbird D/BlankFragment1: onActivityCreated 2022-03-18 16:02:30.585 2410-2410/com.itbird D/BlankFragment1: onStart 2022-03-18 16:02:30.590 2410-2410/com.itbird D/BlankFragment1: onResume 2022-03-18 16:02:35.302 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45 id=0x7f0801b7) 2022-03-18 16:02:35.308 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:35.312 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:37.212 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45 id=0x7f0801b7) 2022-03-18 16:02:37.219 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:37.220 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:39.500 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45 id=0x7f0801b7) 2022-03-18 16:02:39.509 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:39.510 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:41.125 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45 id=0x7f0801b7) 2022-03-18 16:02:41.126 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:41.130 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:50.987 2410-2410/com.itbird D/FragmentTestActivity: fragment2 = BlankFragment2{8536d4a} (05586a4a-4abd-4d28-8ad4-2630c17a50c5) 2022-03-18 16:02:50.999 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged 2022-03-18 16:02:51.000 2410-2410/com.itbird D/BlankFragment2: onAttach 2022-03-18 16:02:51.000 2410-2410/com.itbird D/BlankFragment2: onCreate 2022-03-18 16:02:51.002 2410-2410/com.itbird D/BlankFragment2: onCreateView 2022-03-18 16:02:51.004 2410-2410/com.itbird D/BlankFragment2: onViewCreated 2022-03-18 16:02:51.005 2410-2410/com.itbird D/BlankFragment2: onActivityCreated 2022-03-18 16:02:51.005 2410-2410/com.itbird D/BlankFragment2: onStart 2022-03-18 16:02:51.007 2410-2410/com.itbird D/BlankFragment2: onResume

2022-03-18 16:02:55.315 2410-2410/com.itbird D/FragmentTestActivity: fragment2 = BlankFragment2{8536d4a} (05586a4a-4abd-4d28-8ad4-2630c17a50c5 id=0x7f0801b7) 2022-03-18 16:02:55.316 2410-2410/com.itbird D/BlankFragment2: onHiddenChanged 2022-03-18 16:02:55.317 2410-2410/com.itbird D/BlankFragment2: onHiddenChanged 2022-03-18 16:02:57.005 2410-2410/com.itbird D/FragmentTestActivity: fragment1 = BlankFragment1{21ae25c} (4a8180fe-e646-4cca-89a5-0f617e61eb45 id=0x7f0801b7) 2022-03-18 16:02:57.024 2410-2410/com.itbird D/BlankFragment2: onHiddenChanged 2022-03-18 16:02:57.025 2410-2410/com.itbird D/BlankFragment1: onHiddenChanged

```

總結

1.Fragment是Google官方引入的一個為了適配大屏、多頁面的一個元件。您可以理解為它就是一個類而已,只不過裡面包含了View,並且與activity的生命週期進行了關聯。 2.動態載入與靜態載入相對來說,建議使用動態載入,靜態載入固定在了xml檔案中,永遠不變。 3.replace的fragment如果不在前臺,會執行所有生命週期,反之不會執行任何生命週期方法;hide+show生命週期並不會發生變化,但是會回撥onHiddenChanged方法,在實際開發中,建議add之後,使用hide+show來操作fragment,一方面減少資源的重複載入和建立,另外一方面提升使用者體驗感。 4.fragment的生命週期大體上和activity一致,但是前期和後期多了一些東西,因為fragment內部有view,那麼這個view需要進行建立、然後新增到activity內部,所以相應的在onCreate與onStart之間就多了幾個方法-onCreateView、onViewCreated。相同的道理,fragment的view與activity解綁,也相應的在onStop與onDestory之間多個方法-onDestroyView。onAttach與onDetach可以理解為檢視與activity產生關聯和接觸關聯,是最開始和最後的步驟。

Demo地址