Android元件之Fragment(一)---基礎知識與運用
開啟掘金成長之旅!這是我參與「掘金日新計劃 · 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產生關聯和接觸關聯,是最開始和最後的步驟。