Fragment是在API 11(Android3.0)引入的,為了能夠支援適配寬屏裝置,提供靈活多變的UI設計。
Fragment是嵌在Activity裡面能夠互動的使用者介面,它的存在必須依賴於Activity,不能獨立存在。
多個Activity可以複用同一個Fragment。一個Activity可以嵌入多個Fragment。
Fragment的生命週期
Fragment的生命週期如上圖所示。
加入Activity的話則如上圖所示。
如果不加入Activity的生命週期的話,則為: onAttach()->onCreate()->onCreateView()->onStart()->onResume()->Fragment處於活動狀態 onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()
其中onDestroyView()->onCreateView(),發生此種情況的條件是當Fragment從回退棧(back stack)返回時才會發生。例如:Activity啟動了Fragment A,然後又啟動了FragmentB替換了Fragment A並把A新增到回退棧,當B返回後,退回到A,A的生命週期從onDestroyView()->onCreateView()開始。
幾個回撥方法
onCreate(),可以在這裡進行初始化
onCreateView(),開始對Fragment介面進行繪製,需要返回一個View,通常為Fragment的佈局。
onPause(),當呼叫到此方法時,說明使用者正在離開Fragment,在這裡需要對資料和狀態進行儲存
啟動:
宿主BaseActivity切換到後臺(Activity不可見): 宿主BaseActivity切換回前臺: 宿主BaseActivity銷燬:Fragment的建立
通常的只需要繼承實現Fragment類,為了保證相容性,一般使用support包的Fragment。
public class BaseFragment extends Fragment{
onCreate(){
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_article_list, container, false);
}
}
複製程式碼
Fragment新增到Activity的兩種方法
1、通過在Activity的佈局中宣告fragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
複製程式碼
其中中的android:name屬性的值為Fragment的類全名。
此時Activity可直接載入佈局中的Fragment
為了給Fragment設定一個唯一的標識,可以使用如下兩種方式:
- android:id,指定id屬性為唯一的ID
- android:tag,指定tag屬性作為唯一的標識,為字串型別
設定唯一標識可以對資料恢復有幫助。
2、通過程式碼動態新增Fragment
可以通過指定Activity佈局中的ViewGroup佈局來放置Fragment。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<FrameLayout
android:id="@+id/fl_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
...
</LinearLayout>
複製程式碼
Activity建議使用擴充套件包中的FragmentActivity的子類
public class BaseActivity extends FragmentActivity{
...
//獲取FragmentTransaction
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
...
//新增Fragment
BaseFragment fragment = new BaseFragment();
fragmentTransaction.add(R.id.fl_fragment, fragment);
fragmentTransaction.commit();
...
}
複製程式碼
此外,還可以通過FragmentTransaction的add的過載方法add(Fragment fragment,String tag)新增無介面的Fragment。此時的tag引數為Fragment的標識ID,要取唯一值。並且不需要實現onCreateView()。由於此時Fragment無介面,所以不會對Activity的當前介面造成影響。
如果需要獲取到無介面Fragment,可以使用fragmentManager.findFragmentByTag(tag)。
Fragment的管理
可以使用FragmentTransaction對Fragment進行管理,進行
- 新增 add(),
- 刪除 remove(),
- 替換 replace()
- 加入回退棧 addToBackStack()
操作完後必須呼叫commit()才會生效。
一次典型的操作:
Fragment newFragment = new BaseFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//替換成新的Fragment
transaction.replace(R.id.fragment_container, newFragment);
//把當前的FragmentTransaction加入到回退棧
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
複製程式碼
transaction.addToBackStack(null);的作用是把當前FragmentTransaction加入到回退棧,當執行此程式碼並commit後,則點選回退鍵Activity不會直接結束,而是返回到Activity介面
有幾個點需要注意:
- 一個FragmentTransaction例項只能commit一次,當重複commit時會直接丟擲異常。
- 當呼叫addToBackStack();方法時,會把當前FragmentTransaction例項存入回退棧,可以把Fragment所依賴的Activity看做棧的根。
- addToBackStack(String name)中的引數name可以為null,主要作用是標識此FragmentTransaction例項,用來做區分。
- add()和replace()的區別。add()會新增新的Fragment例項進去,不會對之前的Fragment例項進行銷燬,後面新增的會覆蓋前面新增的,不過如果add相同的Fragment例項,則會丟擲異常。replace()會先把對應佈局上所有的Fragment例項銷燬掉,然後再把新的Fragment例項新增進去。
下圖為Fragment的返回棧,當前的FragmentTransaction例項呼叫addToBackStack()並commit就會入棧:
返回的話會從棧頂開始返回,最上層的會銷燬
Fragment之間,以及和Activity間的資料互動
一個Fragment的例項是和它所繫結的Activity例項緊緊關聯的。Fragment可以通過getActivity()獲取它所關聯繫結的Activity。
和Activity互動
- Activity可以通過Fragment的構造方法把資料傳遞給
- 可通過getActivity()獲取到Activity的例項
- 通過回撥介面可以把Fragment的事件結果傳遞給Activity
Fragment間的互動
- Fragment間的互動可以通過Activity作中轉,
- 可以直接在Fragment通過getFragmentManager()獲取到FragmentManager
官網例子
寬屏佈局 res/layout-land/fragment_layout.xml,用於大螢幕裝置,比如平板電腦等:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
android:id="@+id/titles" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="@+id/details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
複製程式碼
手機佈局 res/layout/fragment_layout.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
android:id="@+id/titles"
android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
複製程式碼
TitlesFragment 的實現
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 新增文章列表
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// 檢查是否上圖左邊的那種佈局(寬屏模式)
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* 如果是寬屏模式的話,則直接在右邊的Fragment更新內容
* 如果不是寬屏模式,則跳轉到新的Activity進行展示
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
getListView().setItemChecked(index, true);
DetailsFragment details = (DetailsFragment)
getSupportFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
details = DetailsFragment.newInstance(index);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (index == 0) {
ft.replace(R.id.details, details);
} else {
ft.replace(R.id.a_item, details);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
}
複製程式碼
DetailsFragment的實現:
public static class DetailsFragment extends Fragment {
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
public int getShownIndex() {
return getArguments().getInt("index", 0);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment's containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
ScrollView scroller = new ScrollView(getActivity());
TextView text = new TextView(getActivity());
int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
4, getActivity().getResources().getDisplayMetrics());
text.setPadding(padding, padding, padding, padding);
scroller.addView(text);
text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
return scroller;
}
}
複製程式碼
如果是手機的情況,跳轉到新的Activity:
public static class DetailsActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line with the list so we don't need this activity.
finish();
return;
}
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
}
}
複製程式碼
--End--