概述
Fragment是 Android 3.0(API 11)引入的一種設計,用於大屏幕的設備。
- Fragment依托於Activity,受宿主Activity生命周期的影響。但它也有自己的生命周期。
- Fragment可重復使用,一個Activity可以有多個Fragment。一個Fragment可以被多個Acitivy使用。
- Fragment在Acitivity運行時可以動態的加載或刪除。在不同分辨率設備或者橫豎屏時 調用對應的Fragment布局就能很好的實現設備的適配,提升用戶體驗。
注:
- AndroidX出來后,使用的Fragment庫就在androidx中,下面的例子都是androidx的。
- Fragment添加到Activity,一種通過<fragment>元素插入到布局中,另一種通過代碼插入到布局中的<FrameLayout>。下面的例子就包含這兩種。
- savedInstanceState這個參數在很多時候是很有用的,在例子中的AnimeDetailFragment中簡單的演示了它的使用。
- 注意不同的設備適配合適的布局,能夠很好的提升用戶體驗。
生命周期
如圖,比較詳細,稍微了解點或者熟悉Activity的都能直接看懂,下面例子中也通過log大致顯示了這一過程。
基本使用
先看下例子的效果,這個例子只有一個Activity 和 兩個Fragment組成:
上述效果,Activity布局中 是兩個fragment,左側的標題欄和右側的詳情界面。左側的標題使用的<fragment>元素直接插入的,右側的詳情界面 通過點擊動態加載的。
Activity布局文件activity_main.xml如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <fragment android:name="com.flx.testfragment.AnimeTitleFragment" android:id="@+id/anime_title_fragment" android:layout_width="120dp" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/anime_detail_fragment_layout" android:layout_toRightOf="@id/anime_title_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00BCD4" /> </RelativeLayout>
Activity很簡單:
這里繼承了androidx.fragment.app.FragmentActivity。
package com.flx.testfragment; import android.os.Bundle; import android.util.Log; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentTransaction; public class MainActivity extends FragmentActivity implements AnimeTitleFragment.OnAmimeSelectedListener { private static final String TAG = "flx_fragment"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_main ); } @Override public void onAnimeSelected(int position) { Log.d( TAG, "onArticleSelected: position=" + position ); //創建一個詳情界面(detailFragment),並出入參數position AnimeDetailFragment detailFragment = new AnimeDetailFragment(); Bundle args = new Bundle(); args.putInt(AnimeDetailFragment.ARG_POSITION, position); detailFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); //用detailFragment替換anime_detail_fragment_layout中的內容 transaction.replace(R.id.anime_detail_fragment_layout, detailFragment); //將事務添加到返回棧中,允許用戶通過按返回按鈕返回上個fragment狀態。該返回棧由Activity管理。 transaction.addToBackStack(null); //提交事務 transaction.commit(); } }
AnimeTitleFragment.java:
在布局文件中通過<fragment>元素插入的,在Activity創建時即生成,即效果圖中的左側標題欄。這里通過繼承ListFragment實現,數據和布局使用ArrayAdapter實現關聯(Adapter不太了解的,可以參考我對應的其他文章),同時這里的layout考慮了一點兼容性。具體代碼如下:
package com.flx.testfragment; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; public class AnimeTitleFragment extends ListFragment { AnimeTitleFragment.OnAmimeSelectedListener mCallback; //Activity實現這個接口 public interface OnAmimeSelectedListener { void onAnimeSelected(int position); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // We need to use a different list item layout for devices older than Honeycomb int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? android.R.layout.simple_list_item_activated_1 : android.R.layout.simple_list_item_1; String[] anime_names = this.getResources().getStringArray( R.array.anime_name ); //使用ArrayAdapter顯示所有標題數據 setListAdapter(new ArrayAdapter<String>(getActivity(), layout, anime_names)); } @Override public void onAttach(Context context) { super.onAttach(context); // 確保Activity實現該接口 try { mCallback = (AnimeTitleFragment.OnAmimeSelectedListener) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement OnAmimeSelectedListener"); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { //傳入position數據,即選中了那個 mCallback.onAnimeSelected(position); getListView().setItemChecked(position, true); } }
AnimeDetailFragment.java:
效果圖中的詳情界面,根據點擊不同的標題顯示不同的內容。這里簡單使用了savedInstanceState,合理的使用對於某些場景是很有幫助的。具體代碼:
package com.flx.testfragment; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; public class AnimeDetailFragment extends Fragment { private static final String TAG = "flx_AnimeDetailFragment"; public static final String ARG_POSITION = "position"; private int mCurrentPosition = -1; public static String[] ANIME_NAMES; public static String[] ANIME_AUTHORS; public static int[] COVER_IMGS = {R.drawable.hzw1, R.drawable.jjdjr1, R.drawable.hyrz1, R.drawable.zchzt1, R.drawable.qsmy1, R.drawable.xyj1, R.drawable.hlw1}; private TextView mAnimeName; private TextView mAnimeAuthor; private ImageView mCoverImg; @Override public void onAttach(Context context) { Log.d( TAG, "onAttach: " + this ); super.onAttach( context ); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { Log.d( TAG, "onCreate: " + this ); super.onCreate( savedInstanceState ); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d( TAG, "onCreateView: " + this ); ANIME_NAMES = getActivity().getResources().getStringArray( R.array.anime_name ); ANIME_AUTHORS = getActivity().getResources().getStringArray( R.array.anime_author ); //savedInstanceState,這個參數很有用,在activity被重新創建時(如旋轉屏幕)等,恢復之前的狀態。 if (savedInstanceState != null) { mCurrentPosition = savedInstanceState.getInt(ARG_POSITION); } View view = inflater.inflate( R.layout.anime_detail_view, container, false ); mAnimeName = view.findViewById( R.id.anime_name_txt ); mAnimeAuthor = view.findViewById( R.id.anime_author_txt ); mCoverImg = view.findViewById( R.id.anime_cover_img ); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { Log.d( TAG, "onActivityCreated: " + this); super.onActivityCreated( savedInstanceState ); } @Override public void onStart() { Log.d( TAG, "onStart: " + this ); super.onStart(); Bundle args = getArguments(); if (args != null) { //根據傳入的參數ARG_POSITION 更新 updateAnimeDetailView(args.getInt(ARG_POSITION)); } else if (mCurrentPosition != -1) { // Set article based on saved instance state defined during onCreateView //根據保存的狀態中信息 更新 updateAnimeDetailView(mCurrentPosition); } } @Override public void onResume() { Log.d( TAG, "onResume: " + this ); super.onResume(); } @Override public void onSaveInstanceState(Bundle outState) { Log.d( TAG, "onSaveInstanceState: " + this ); super.onSaveInstanceState(outState); //保存fragment當前狀態的信息。 outState.putInt(ARG_POSITION, mCurrentPosition); } public void updateAnimeDetailView(int position) { mAnimeName.setText(ANIME_NAMES[position]); mAnimeAuthor.setText(ANIME_AUTHORS[position]); mCoverImg.setImageResource(COVER_IMGS[position]); mCurrentPosition = position; } @Override public void onPause() { Log.d( TAG, "onPause: " + this ); super.onPause(); } @Override public void onStop() { Log.d( TAG, "onStop: " + this ); super.onStop(); } @Override public void onDestroyView() { Log.d( TAG, "onDestroyView: " + this ); super.onDestroyView(); } @Override public void onDestroy() { Log.d( TAG, "onDestroy: " + this ); super.onDestroy(); } @Override public void onDetach() { Log.d( TAG, "onDetach: " + this ); super.onDetach(); } }
一些資源文件:
AnimeDetailFragment的布局文件:anime_detail_view.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/anime_cover_img" android:layout_width="match_parent" android:layout_height="150dp" android:layout_margin="10dp" android:scaleType="fitXY" /> <TextView android:id="@+id/anime_name_txt" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center" android:textStyle="bold" android:textSize="20sp"/> <TextView android:id="@+id/anime_author_txt" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center" android:textStyle="italic" android:textSize="15sp"/> </LinearLayout>
strings.xml:
<array name="anime_name"> <item>海賊王</item> <item>進擊的巨人</item> <item>火影忍者</item> <item>斬赤紅之瞳</item> <item>秦時明月</item> <item>西游記</item> <item>葫蘆娃</item> </array> <array name="anime_author"> <item>尾田榮一郎</item> <item>諫山創</item> <item>岸本齊史</item> <item>タカヒロ</item> <item>玄機科技</item> <item>央視</item> <item>上海美術電影</item> </array>
生命周期流程
最后,看下log,看看fragment的生命周期
操作:點擊第一個標題(海賊王),然后點擊第二個標題(進擊的巨人),最后點擊返回鍵,下面log的過程 與 生命周期那個圖 是一致的,不太明白的,可以仔細看下如下log。
//點擊第一個標題,創建了fragment1(52888e9) 2020-03-03 16:22:48.316 20442-20442/com.flx.testfragment D/flx_fragment: onArticleSelected: position=0 2020-03-03 16:22:48.344 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onAttach: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:48.344 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreate: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:48.347 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:48.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:48.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:48.357 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{52888e9 #1 id=0x7f090020} //點擊第二個標題,創建了fragment2(371a2d2), fragment1(52888e9)進入生命周期到onDestroyView 2020-03-03 16:22:51.346 20442-20442/com.flx.testfragment D/flx_fragment: onArticleSelected: position=1 2020-03-03 16:22:51.349 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onAttach: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:51.349 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreate: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onPause: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStop: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroyView: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:51.365 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:51.369 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:51.369 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:51.391 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} //按了返回鍵,移除了fragment2(371a2d2), fragment1(52888e9)重新恢復。fragment2(371a2d2)執行到onDetach 被銷毀。 2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onPause: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStop: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroyView: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroy: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDetach: AnimeDetailFragment{371a2d2 #2 id=0x7f090020} 2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:53.150 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:53.150 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{52888e9 #1 id=0x7f090020} 2020-03-03 16:22:53.151 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
Fragment使用並不難,用好了,能構建很靈活的界面,做到很好的適配,提升用戶體驗。