概述
Fragment是 Android 3.0(API 11)引入的一种设计,用于大屏幕的设备。
Fragment依托于Activity,受宿主Activity生命周期的影响。但它也有自己的生命周期。
Fragment可重复使用,一个Activity可以有多个Fragment。一个Fragment可以被多个Acitivy使用。
Fragment在Acitivity运行时可以动态的加载或删除。在不同分辨率设备或者横竖屏时 调用对应的Fragment布局就能很好的实现设备的适配,提升用户体验。
注:
AndroidX出来后,使用的Fragment库就在androidx中,下面的例子都是androidx的。
Fragment添加到Activity,一种通过元素插入到布局中,另一种通过代码插入到布局中的。下面的例子就包含这两种。
savedInstanceState这个参数在很多时候是很有用的,在例子中的AnimeDetailFragment中简单的演示了它的使用。
注意不同的设备适配合适的布局,能够很好的提升用户体验。
生命周期
如图,比较详细,稍微了解点或者熟悉Activity的都能直接看懂,下面例子中也通过log大致显示了这一过程。
基本使用
先看下例子的效果,这个例子只有一个Activity 和 两个Fragment组成:
上述效果,Activity布局中 是两个fragment,左侧的标题栏和右侧的详情界面。左侧的标题使用的元素直接插入的,右侧的详情界面 通过点击动态加载的。
Activity布局文件activity_main.xml如下:
Activity很简单:
这里继承了androidx.fragment.app.FragmentActivity。
packagecom.flx.testfragment;importandroid.os.Bundle;importandroid.util.Log;importandroidx.fragment.app.FragmentActivity;importandroidx.fragment.app.FragmentTransaction;public class MainActivity extends FragmentActivity implementsAnimeTitleFragment.OnAmimeSelectedListener {private static final String TAG = "flx_fragment";
@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
}
@Overridepublic void onAnimeSelected(intposition) {
Log.d( TAG,"onArticleSelected: position=" +position );//创建一个详情界面(detailFragment),并出入参数position
AnimeDetailFragment detailFragment = newAnimeDetailFragment();
Bundle args= newBundle();
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:
在布局文件中通过元素插入的,在Activity创建时即生成,即效果图中的左侧标题栏。这里通过继承ListFragment实现,数据和布局使用ArrayAdapter实现关联(Adapter不太了解的,可以参考我对应的其他文章),同时这里的layout考虑了一点兼容性。具体代码如下:
packagecom.flx.testfragment;importandroid.content.Context;importandroid.os.Build;importandroid.os.Bundle;importandroid.util.Log;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.ArrayAdapter;importandroid.widget.ListView;importandroidx.annotation.Nullable;importandroidx.fragment.app.ListFragment;public class AnimeTitleFragment extendsListFragment {
AnimeTitleFragment.OnAmimeSelectedListener mCallback;//Activity实现这个接口
public interfaceOnAmimeSelectedListener {void onAnimeSelected(intposition);
}
@Overridepublic voidonCreate(@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(getActivity(), layout, anime_names));
}
@Overridepublic voidonAttach(Context context) {super.onAttach(context);//确保Activity实现该接口
try{
mCallback=(AnimeTitleFragment.OnAmimeSelectedListener) context;
}catch(ClassCastException e) {throw newClassCastException(context.toString()+ " must implement OnAmimeSelectedListener");
}
}
@Overridepublic void onListItemClick(ListView l, View v, int position, longid) {//传入position数据,即选中了那个
mCallback.onAnimeSelected(position);
getListView().setItemChecked(position,true);
}
}
AnimeDetailFragment.java:
效果图中的详情界面,根据点击不同的标题显示不同的内容。这里简单使用了savedInstanceState,合理的使用对于某些场景是很有帮助的。具体代码:
packagecom.flx.testfragment;importandroid.content.Context;importandroid.os.Bundle;importandroid.util.Log;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.ImageView;importandroid.widget.TextView;importandroidx.annotation.NonNull;importandroidx.annotation.Nullable;importandroidx.fragment.app.Fragment;public class AnimeDetailFragment extendsFragment {private static final String TAG = "flx_AnimeDetailFragment";public static final String ARG_POSITION = "position";private int mCurrentPosition = -1;public staticString[] ANIME_NAMES;public staticString[] 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};privateTextView mAnimeName;privateTextView mAnimeAuthor;privateImageView mCoverImg;
@Overridepublic voidonAttach(Context context) {
Log.d( TAG,"onAttach: " + this);super.onAttach( context );
}
@Overridepublic voidonCreate(@Nullable Bundle savedInstanceState) {
Log.d( TAG,"onCreate: " + this);super.onCreate( savedInstanceState );
}
@Nullable
@OverridepublicView 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 );returnview;
}
@Overridepublic voidonActivityCreated(@Nullable Bundle savedInstanceState) {
Log.d( TAG,"onActivityCreated: " + this);super.onActivityCreated( savedInstanceState );
}
@Overridepublic voidonStart() {
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);
}
}
@Overridepublic voidonResume() {
Log.d( TAG,"onResume: " + this);super.onResume();
}
@Overridepublic voidonSaveInstanceState(Bundle outState) {
Log.d( TAG,"onSaveInstanceState: " + this);super.onSaveInstanceState(outState);//保存fragment当前状态的信息。
outState.putInt(ARG_POSITION, mCurrentPosition);
}public void updateAnimeDetailView(intposition) {
mAnimeName.setText(ANIME_NAMES[position]);
mAnimeAuthor.setText(ANIME_AUTHORS[position]);
mCoverImg.setImageResource(COVER_IMGS[position]);
mCurrentPosition=position;
}
@Overridepublic voidonPause() {
Log.d( TAG,"onPause: " + this);super.onPause();
}
@Overridepublic voidonStop() {
Log.d( TAG,"onStop: " + this);super.onStop();
}
@Overridepublic voidonDestroyView() {
Log.d( TAG,"onDestroyView: " + this);super.onDestroyView();
}
@Overridepublic voidonDestroy() {
Log.d( TAG,"onDestroy: " + this);super.onDestroy();
}
@Overridepublic voidonDetach() {
Log.d( TAG,"onDetach: " + this);super.onDetach();
}
}
一些资源文件:
AnimeDetailFragment的布局文件:anime_detail_view.xml:
strings.xml:
海贼王
进击的巨人
火影忍者
斩赤红之瞳
秦时明月
西游记
葫芦娃
尾田荣一郎
谏山创
岸本齐史
タカヒロ
玄机科技
央视
上海美术电影
生命周期流程
最后,看下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使用并不难,用好了,能构建很灵活的界面,做到很好的适配,提升用户体验。