1. Fragment概述
1.1 介紹
Fragment是一種可以嵌入在活動中的UI片段,能夠讓程序更加合理和充分地利用大屏幕的空間,出現的初衷是為了適應大屏幕的平板電腦,可以將其看成一個小型Activity,又稱作Activity片段。
使用Fragment可以把屏幕划分成幾塊,然后進行分組,進行一個模塊化管理。Fragment不能夠單獨使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影響
官方定義如下:
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.
從官方的定義可以得到:
- Fragment依賴於Activity,不能獨立存在
- 一個Activity可以有多個Fragment
- 一個Fragment可以被多個Activity重用
- Fragment有自己的生命周期,並能接收輸入事件
- 可以在Activity運行時動態地添加或刪除Fragment
Fragment的優勢:
- 模塊化(Modularity):我們不必把所有代碼全部寫在Activity中,而是把代碼寫在各自的Fragment中。
- 可重用(Reusability):多個Activity可以重用一個Fragment。
- 可適配(Adaptability):根據硬件的屏幕尺寸、屏幕方向,能夠方便地實現不同的布局,這樣用戶體驗更好。
1.2 Fragment基本生命周期

Fragment的一般生命周期如上圖所示:
- onAttach():Fragment和Activity相關聯時調用。可以通過該方法獲取Activity引用,還可以通過getArguments()獲取參數。
- onCreate():Fragment被創建時調用
- onActivityCreated():當Activity完成onCreate()時調用
- onStart():當Fragment可見時調用。
- onResume():當Fragment可見且可交互時調用
- onPause():當Fragment不可交互但可見時調用。
- onStop():當Fragment不可見時調用。
- onDestroyView():當Fragment的UI從視圖結構中移除時調用。
- onDestroy():銷毀Fragment時調用。
- onDetach():當Fragment和Activity解除關聯時調用。
Fragment生命周期會經歷:運行、暫停、停止、銷毀。
- 運行狀態:碎片可見時,關聯活動處於運行狀態,其也為運行狀態
- 暫停狀態:活動進入暫停狀態,相關聯可見碎片就會進入暫停狀態
- 停止狀態:活動進入停止狀態,相關聯碎片就會進入停止狀態,或者通過FragmentTransaction的remove()、replace()方法將碎片從從活動中移除,但如果在事務提交之前調用addToBackStack()方法,這時的碎片也會進入到停止狀態。
- 銷毀狀態:當活動被銷毀,相關聯碎片進入銷毀狀態。或者調用FragmentTransaction的remove()、replace()方法將碎片從活動中移除,但在事務提交之前並沒有調用addToBackStack()方法,碎片也會進入到銷毀狀態。
在介紹Fragment的具體使用時,先介紹一下Fragment的幾個核心類
- Fragment:Fragment的基類,任何創建的Fragment都需要繼承該類
- FragmentManager:管理和維護Fragment。他是抽象類,具體的實現類是FragmentManagerImpl。
- FragmentTransaction:對Fragment的添加、刪除等操作都需要通過事務方式進行。他是抽象類,具體的實現類是BackStackRecord
擴展子類:
- 對話框:DialogFragment
- 列表:ListFragment
- 選項設置:PreferenceFragment
- WebView界面:WebViewFragment
備注:開發Fragment不建議使用android.app下的Fragment而應是android:support.v4.app,因為support庫是不斷更新的。
2. Fragment使用
使用Fragment有兩種方式,分別是靜態加載和動態加載
2.1 靜態加載
關於靜態加載的流程如下:
- 定義Fragment的xml布局文件
- 自定義Fragment類,繼承Fragment類或其子類,同時實現onCreate()方法,在方法中,通過inflater.inflate加載布局文件,接着返回其View
- 在需要加載Fragment的Activity對應布局文件中
的name屬性設為全限定類名,即包名.fragment - 最后在Activity調用setContentView()加載布局文件即可
靜態加載一旦添加就不能在運行時刪除
示例:
- 定義Fragment布局,新建left_fragment.xml和right_fragment.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/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="20sp"
android:text="this is Fragment" />
</LinearLayout>
- 自定義Fragment類,繼承Fragment或其子類,重寫onCreateView(),在方法中調用inflater.inflate()方法加載Fragment布局文件,接着返回加載的view對象
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment, container,false);
return view;
}
}
public class RigthFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, container, false);
return view;
}
}
- 在需要加載Fragment的Activity對應的布局文件中添加Fragment標簽
<fragment
android:id="@+id/left_fragment"
android:name="com.vivo.a11085273.secondfragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/right_fragment"
android:name="com.vivo.a11085273.secondfragmenttest.RigthFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
- 在Activity的onCreate()方法中調用setContentView()加載布局文件即可
2.2 動態加載Fragment
動態加載Fragment的流程如下:
- 獲得FragmentManager對象,通過getSupportFragmentManager()
- 獲得FragmentTransaction對象,通過fm.beginTransaction()
- 調用add()方法或者repalce()方法加載Fragment;
- 最后調用commit()方法提交事務
簡單示例:
- 同靜態加載一樣,首先定義Fragment的布局和類,修改主布局文件,不指定
標簽的name屬性。 - 實現Fragment調用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
replaceFragment(new RigthFragment());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
default:
break;
}
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction(); // 開啟一個事務
transaction.replace(R.id.right_layout, fragment);
transaction.commit();
}
}
2.3 使用注意點
- Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三個參數是false,因為在Fragment內部實現中,會把該布局添加到container中,如果設為true,那么就會重復做兩次添加,則會拋如下異常:
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
- 如果在創建Fragment時要傳入參數,必須要通過setArguments(Bundle bundle)方式添加,而不建議通過為Fragment添加帶參數的構造函數,因為通過setArguments()方式添加,在由於內存緊張導致Fragment被系統殺掉並恢復(re-instantiate)時能保留這些數據
可以在Fragment的onAttach()中通過getArguments()獲得傳進來的參數。如果要獲取Activity對象,不建議調用getActivity(),而是在onAttach()中將Context對象強轉為Activity對象
示例:
public class Fragment1 extends Fragment{
private static String ARG_PARAM = "param_key";
private String mParam;
private Activity mActivity;
public void onAttach(Context context) {
mActivity = (Activity) context;
mParam = getArguments().getString(ARG_PARAM); //獲取參數
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_1, container, false);
TextView view = root.findViewById(R.id.text);
view.setText(mParam);
return root;
}
public static Fragment1 newInstance(String str) {
Fragment1 frag = new Fragment1();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle); //設置參數
return fragment;
}
}
-
動態加載Fragment中,FragmentTransaction類提供了方法完成增刪等操作,完成后調用FragmentTransaction.commit()方法提交修改
- transaction.add():往Activity里面添加一個片段
- transaction.remove():從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧,這個Fragment實例將會被銷毀
- transaction.replace():使用另一個Fragment替換當前的,實際上是remove()然后add()的合體
- transaction.hide():隱藏當前Fragment,僅不可見,不會銷毀
- transaction.show():顯示之前隱藏的Fragment
- detach():會將view從UI中移除,和remove()不同,此時fragment的狀態依然由FragmentManager維護
- attach():重建view視圖,附加到UI上並顯示。
commit方法一定要在Activity.onSaveInstance()之前調用
commit()操作是異步的,內部通過mManager.enqueueAction()加入處理隊列。對應的同步方法為commitNow(),commit()內部會有checkStateLoss()操作,如果開發人員使用不當(比如commit()操作在onSaveInstanceState()之后),可能會拋出異常,而commitAllowingStateLoss()方法則是不會拋出異常版本的commit()方法,但是盡量使用commit(),而不要使用commitAllowingStateLoss()。
-
FragmentManager擁有回退棧(BackStack),類似於Activity的任務棧,如果添加了該語句,就把該事務加入回退棧,當用戶點擊返回按鈕,會回退該事務(回退指的是如果事務是add(frag1),那么回退操作就是remove(frag1));如果沒添加該語句,用戶點擊返回按鈕會直接銷毀Activity。
-
Fragment常見異常
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
出現原因:commit()在onSaveInstanceState()后調用。
由於onSaveInstanceState()在onPause()之后,onStop()之前調用。onRestoreInstanceState()在onStart()之后,onResume()之前,因此避免出現該異常的方案有:
- 不要把Fragment事務放在異步線程的回調中
- 逼不得已時使用commitAllowingStateLoss()
3. Fragment與Activity聯動

Fragment和Activity完整的生命周期如上圖所示
- 當Fragment在Activity的onCreate()中被添加時
- Activity super.onCreate執行完畢
- Fragment onAttach
- Fragment onCreate
- Fragment onCreateView
- Fragment onViewCreated
- Activity.super.onStart()執行中
- Fragment onActivityCreated
- Fragment onViewStateRestored
- Fragment onStart()
- Activity super.onStart執行完畢
- Activity super.onPostCreate()
- Activity super.onResume()
- Activity super.onPostResume()執行中
- Fragment onResume()
- Activity super.onPosResume()執行完畢
- Activity onAttachedToWindow()
- Activity onCreateOptionsMenu()
- Fragment onCreateOptionsMenu()
- Activity onPrepareOptionsMenu()
- Fragment onPrepareOptionsMenu()
- Activity onWindowFocusChanged()
- 暫停生命周期
- Activity super.onPause()執行中
- Fragment.onPause()
- Activity super.onPause()執行完畢
- Activity super.onSaveInstanceState()執行中
- Fragment onSaveInstanceState()
- Activity super.onSaveInstanceState()執行完畢
- Activity super.onStop()執行中
- Fragment onStop()
- Activity super.onStop()執行完畢
- 銷毀的生命周期
- Activity super.onDestroy()執行中
- Fragment onDestroyView()
- Fragment onDestroy()
- Fragment onDetach()
- Activity super.onDestroy()執行完畢
- 重啟的生命周期
- Activity super.onRestart()
- Activity super.onStart()執行中
- Fragment onStart()
- Activity super.onStart()執行完畢
- Activity super.onResume()
- Activity super.onPostResume()執行中
- Fragment onResume()
- Activity super.onPosResume()執行完畢
- Activity onWindowFocusChanged()執行完畢
3.1 回退棧
類似Android系統為Activity維護一個任務棧,我們也可以通過Activity維護一個回退棧來保存每次Fragment事務發生的變化。如果你將Fragment任務添加到回退棧,當用戶點擊后退按鈕時,將看到上一次的保存的Fragment。
一旦Fragment完全從后退棧中彈出,用戶再次點擊后退鍵,則退出當前Activity
添加一個Fragment事務到回退棧:
FragmentTransaction.addToBackStack(String)
簡單示例:
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null); //添加進回退棧
transaction.commit();
}
replace是remove和add的合體,並且如果不添加事務到回退棧,前一個Fragment實例會被銷毀。這里很明顯,我們調用transaction.addToBackStack(null);將當前的事務添加到了回退棧,所以FragmentOne實例不會被銷毀,但是視圖層次依然會被銷毀,即會調用onDestoryView和onCreateView
如果不希望視圖重繪,可以將原來的Fragment隱藏:
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.hide(this);
transaction.add(R.id.right_layout, fragment);
transaction.addToBackStack(null); //添加進回退棧
transaction.commit();
}
4. Fragment與Activity通信

Fragment與Activity的通信交互如上圖所示:
- 如果Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那么沒關系,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然后進行操作
- 在Fragment中可以通過getActivity得到當前綁定的Activity的實例,然后進行操作。
備注:
- 如果在Fragment中需要Context,可以通過getActivity(),如果該Context需要在Activity被銷毀后還存在,則使用getActivity.getApplicationContext();
考慮Fragment的重復使用問題,降低與Activity的耦合,Fragment操作應該由它的管理者Activity決定。
4.1 傳遞數據給Fragment
步驟流程:
- 在Activity中創建Bundle數據包,調用Fragment實例的setArguments(),將Bundle數據包傳給Fragment
- Fragment調用getArguments()獲得Bundle對象,然后進行解析就可以
簡單示例:
//創建Fragment對象,並通過Bundle對象傳遞值(在onCreate方法中)
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("key", values);
fragment.setArguments(bundle);
//(在Fragment類中的onCreateView方法中)
Bundle bundle = this.getArguments();
if (bundle != null)
{
String str = bundle.getString("key");
}
TextView textView = new TextView(getActivity());
textView.setText("上上下下的享受");//是電梯,別誤會
4.2 傳遞數據給Activity
步驟流程:
- 在Fragment中定義一個內部回調接口,再讓包含該Fragment的Activity實現該回調接口
- Fragment通過回調接口傳數據
簡單示例:
- 首先在Fragment中定義一個接口(定義抽象方法,傳什么類型參數)
/*接口*/
public interface Mylistener{
public void thanks(String code);
}
- Fragment類中定義該接口
private Mylistener listener;
- 在onAttach方法中,將定義的該接口強轉為activity類型
@Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
listener=(Mylistener) activity;
super.onAttach(activity);
}
- Activity只需實現該接口並重寫該方法即可
@Override
public void thanks(String code) {
// TODO Auto-generated method stub
Toast.makeText(this, "已收到Fragment的消息:--"+code+"--,客氣了", Toast.LENGTH_SHORT).show();;
}
除了接口回調,還可以使用EventBus進行交互通信。
5. Fragment間通信
5.1 setArguments()
示例:
- 在Fragment B中新建一個函數:newInstance()接收傳過來的參數
public static Fragment2 newInstance(String text) {
Fragment2 fragment = new Fragment2();
Bundle args = new Bundle();
args.putString("param", text);
fragment.setArguments(args);
return fragment;
}
- 在Fragment B的onCreateView中獲取參數
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment2, container, false);
if (getArguments() != null) {
String mParam1 = getArguments().getString("param");
TextView tv = (TextView)view.findViewById(R.id.textview);
tv.setText(mParam1);
}
return view;
}
- 在Fragment A中,調用Fragment B時,通過newInstance函數獲取實例並傳遞參數:
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container, false);
Button btn = (Button)view.findViewById(R.id.load_fragment2_btn);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(final View view) {
Fragment2 fragment2 = Fragment2.newInstance("從Fragment1傳來的參數");
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.main_layout, fragment2);
transaction.addToBackStack(null);
transaction.commit();
}
});
return view;
}
}
5.2 同Activity不同Container的Fragment交互
這種情況有三中方法解決:
方法一:直接在Activity中操作
直接在Activity中找到對應控件的實例,然后直接操控即可
方法二:直接在Fragment中操作
這里有兩個問題:如何獲取自己控件的引用?如何獲取其他Fragment頁控件的引用?
- 首先獲取自己控件的引用
可以在onCreateView()中獲取
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment1, container, false);
listView = (ListView)rootView.findViewById(R.id.list);//獲取自己視圖里的控件引用,方法一
return rootView;
}
在onCreateView()中,還沒有創建視圖,所以在這里如果使用getView()方法將返回空
另一種方法是在onActivityCreated()中獲取,其回調在onCreate()執行后再執行
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
}
- 獲取其它Fragment頁控件引用方法
獲取Activity資源,須等Activity創建完成后,必須放在onActivityCreated()回調函數中
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!!
}
總的實現示例如下:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!!
listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String str = mStrings[position];
mFragment2_tv.setText(str);
}
});
}
方法三:在各自Fragment中操作
方法二在Fragment A中操作了Fragment B,違背模塊分離思想,應通過Activity將其分離
在Activity中可以直接通過FragmentManager.findFragmentById()獲取Fragment實例
示例:
在Fragment2設置TextView函數
public class Fragment2 extends Fragment {
private TextView mTv;
…………
public void setText(String text) {
mTv.setText(text);
}
}
在Fragment1 中定義處理方式
- 定義接口與變量
private titleSelectInterface mSelectInterface;
public interface titleSelectInterface{
public void onTitleSelect(String title);
}
- 接口變量賦值
接口給Activity使用,在Activity中給接口變量賦值,在Fragment與Activity關聯時,需要強轉
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mSelectInterface = (titleSelectInterface) activity;
} catch (Exception e) {
throw new ClassCastException(activity.toString() + "must implement OnArticleSelectedListener");
}
}
- 調用接口變量
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String str = mStrings[position];
mSelectInterface.onTitleSelect(str);
}
});
}
- 在Activity中實現接口
public class MainActivity extends FragmentActivity implements Fragment1.titleSelectInterface {
……
@Override
public void onTitleSelect(String title) {
FragmentManager manager = getSupportFragmentManager();
Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2);
fragment2.setText(title);
}
}
