Android Fragment 基础
01.什么是Fragment
- 什么是Fragment
- 可以简单的理解为,Fragment是显示在Activity中的Activity。它可以显示在Activity中,然后它也可以显示出一些内容。因为它拥有自己的生命周期,可以接受处理用户的事件,并且你可以在一个Activity中动态的添加,替换,移除不同的Fragment,因此对于信息的展示具有很大的便利性。
- 作为 view 界面的一部分,Fragment 的存在必须依附于 FragmentActivit使用,并且与 FragmentActivit 一样,拥有自己的独立的生命周期,同时处理用户的交互动作。同一个 FragmentActivit 可以有一个或多个 Fragment 作为界面内容,同样Fragment也可以拥有多个子Fragment,并且可以动态添加、删除 Fragment,让UI的重复利用率和易修改性得以提升,同样可以用来解决部分屏幕适配问题。
- Fragment是组件还是控件
- 严格意义上来说Fragment并不是一个显示控件,而只是一个显示组件。为什么这么说呢?其实像我们的Activity,Dialog,PopupWindow以及Toast类的内部都管理维护着一个Window对象,这个Window对象不但是一个View组件的集合管理对象,它也实现了组件的加载与绘制流程,而我们的Fragment组件如果看过源码的话,严格意义上来说,只是一个View组件的集合并通过控制变量实现了其特定的生命周期,但是其由于并没有维护Window类型的成员变量,所以其不具备组件的加载与绘制功能,因此其不能单独的被绘制出来,这也是我把它称之为组件而不是控件的原因。
- 使用条件
- 宿主Activity 必须继承自 FragmentActivity;
- 使用getSupportFragmentManager() 方法获取 FragmentManager 对象;
02.Fragment生命周期
- Fragment是依附于Activity存在的,因此它的生命周期收到Activity的生命周期影响
- Fragment比Activity多了几个生命周期的回调方法
- onAttach(Activity) 当Fragment与Activity发生关联的时候调用
- onCreateView(LayoutInflater, ViewGroup, Bundle) 创建该Fragment的视图
- onViewCreated(View view, Bundle savedInstanceState) 试图创建后调用该方法
- onActivityCreated(Bundle) 当Activity的onCreated方法返回时调用
- onDestroyView() 与onCreateView方法相对应,当该Fragment的视图被移除时调用
- onDetach() 与onAttach方法相对应,当Fragment与Activity取消关联时调用
- 注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现
03.Fragment使用
- Activity创建Fragment的方式是什么?
- 静态创建具体步骤
- 首先我们同样需要注册一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。
- 动态创建具体步骤
- (1)创建待添加的碎片实例
- (2)获取FragmentManager,在活动中可以直接通过调用 getSupportFragmentManager()方法得到。
- (3)开启一个事务,通过调用beginTransaction()方法开启。
- (4)向容器内添加或替换碎片,一般使用repalce()方法实现,需要传入容器的id和待添加的碎片实例。
- (5)提交事务,调用commit()方法来完成。
- 静态创建具体步骤
- 接下来就分别详细地理解一下两种创建fragment的方式的细节。
3.1 静态使用
大概步骤:
- ① 创建一个类继承Fragment,重写onCreateView方法,来确定Fragment要显示的布局
- ② 在Activity中声明该类,与普通的View对象一样
代码演示
- 继承Frgmanet的类MyFragment【请注意导包的时候导v4的Fragment的包】
1
2
3
4
5
6
7
8
9
10
11
12public class MyFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
* 参数1:布局文件的id
* 参数2:容器
* 参数3:是否将这个生成的View添加到这个容器中去
* 作用是将布局文件封装在一个View对象中,并填充到此Fragment中
* */
View v = inflater.inflate(R.layout.item_fragment, container, false);
return v;
}
}- Activity对应的布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.yczbj.fragment.MainActivity">
<fragment
android:id="@+id/search_fragment"
android:name="com.yczbj.fragment.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>- Activity中的显示和隐藏fragment代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27phoneNumFragment = (PhoneNumFragment) getSupportFragmentManager()
.findFragmentById(R.id.search_fragment);
//处理返回键
@Override
public void onBackPressed() {
if (phoneNumFragment != null && !phoneNumFragment.isHidden()) {
hideSearch();
return;
}
super.onBackPressed();
}
private void hideSearch() {
getSupportFragmentManager()
.beginTransaction()
.hide(phoneNumFragment)
.commit();
}
private void showSearch() {
getSupportFragmentManager()
.beginTransaction()
.show(phoneNumFragment)
.commit();
}
3.2 动态使用
代码如下所示,这种是平时开发最常用的
1
2
3
4
5
6
7
8
9
10OrderStatesFragment fragment = new OrderStatesFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("confirmOrderModel",confirmOrderModel);
fragment.setArguments(bundle);
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
//transaction.show(fragment); //暂时不用这个
transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
transaction.commitAllowingStateLoss();部分代码说明
- ① 穿件一个fragment的实例
- ② 通过getSupportFragmentManager()方法新建Fragment管理器对象
- ③ 然后通过Fragment管理器对象调用beginTransaction()方法,实例化FragmentTransaction对象,有人称之为事务
- ④ FragmentTransaction对象【以下直接用transaction代替】,transaction的方法主要有以下几种:
- transaction.add() 向Activity中添加一个Fragment
- transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁
- transaction.replace() 使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
- transaction.hide() 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁,它只会触发onHiddenChange()方法。
- transaction.show() 显示之前隐藏的Fragment,它只会触发onHiddenChange()方法。
- detach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
- attach() 重建view视图,附加到UI上并显示
- ransatcion.commit() 提交事务
- 注意:在add/replace/hide/show以后都要commit其效果才会在屏幕上显示出来
3.3 传递参数
如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26/**
* 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 FirstFragment.
*/
// TODO: Rename and change types and number of parameters
public static FirstFragment newInstance(String param1, String param2) {
FirstFragment fragment = new FirstFragment();
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);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
04.Fragment回退栈
Fragment的回退栈是用来保存每一次Fragment事务发生的变化
- 如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
在某个activity上添加fragment,如果不处理宿主activity中返回键逻辑,点击返回键,关闭了fragment同时也关闭了activity。
1
2
3
4
5
6
7OrderStatesFragment fragment = new OrderStatesFragment();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
//transaction.show(fragment); //暂时不用这个
transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
transaction.commitAllowingStateLoss();在某个activity上添加fragment,如果不处理宿主activity中返回键逻辑,点击返回键,关闭了fragment,回到宿主activity页面。
1
2
3
4
5
6
7
8OrderStatesFragment fragment = new OrderStatesFragment();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
//transaction.show(fragment); //暂时不用这个
transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
transaction.commitAllowingStateLoss();
transaction.addToBackStack(null);
05.Fragment与Activity通信
- Fragment依附于Activity存在,因此与Activity之间的通信可以归纳为以下几点:
- 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作
- Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作。不过不建议这样获取activity的实例,后面会提到的,容易报空指针异常问题。
06.Fragment与Activity通信的优化
因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
针对直接创建fragment,一般可以这样写
1
2
3
4
5
6
7
8
9QrCodeLoginFragment fragment = new QrCodeLoginFragment();
Bundle bundle = new Bundle();
bundle.putString("unique_id", content);
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(android.R.id.content, fragment, "qrCodeLoginFragment");
transaction.commit();
transaction.addToBackStack(null);针对有些频繁show或者hide的fragment,可以这样处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* 展示页面
*/
private void showPlayingFragment() {
if (isPlayFragmentShow) {
return;
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.fragment_slide_up, 0);
if (mPlayFragment == null) {
mPlayFragment = PlayMusicFragment.newInstance("Main");
ft.replace(android.R.id.content, mPlayFragment);
} else {
ft.show(mPlayFragment);
}
ft.commitAllowingStateLoss();
isPlayFragmentShow = true;
}
/**
* 隐藏页面
*/
private void hidePlayingFragment() {
if(mPlayFragment!=null){
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(0, R.anim.fragment_slide_down);
ft.hide(mPlayFragment);
ft.commitAllowingStateLoss();
isPlayFragmentShow = false;
}
}
06.Fragment旋转场景
在Activity的学习中都知道,当屏幕旋转时,是对屏幕上的视图进行了重新绘制。
- 因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建,用脚趾头都明白…横屏和竖屏显示的不一样肯定是进行了重新绘制视图的操作。所以,不断的旋转就不断绘制,这是一种很耗费内存资源的操作,那么如何来进行优化?
首先看看Fragment代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class FragmentOne extends Fragment {
private static final String TAG = "FragmentOne";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.e(TAG, "onCreateView");
View view = inflater.inflate(R.layout.fragment_one, container, false);
return view;
}
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
}
@Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
Log.e(TAG, "onDestroyView");
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.e(TAG, "onDestroy");
}
}- 然后你多次翻转屏幕都会打印如下log
1
2
3
4
5
607-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.681: E/FragmentOne(1633): onCreateView
07-20 08:18:46.831: E/FragmentOne(1633): onCreateView
07-20 08:18:46.891: E/FragmentOne(1633): onCreateView- 因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。
如何解决
- 通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建
- 默认的savedInstanceState会存储一些数据,包括Fragment的实例
- 简单改一下代码,判断只有在savedInstanceState==null时,才进行创建Fragment实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MainActivity extends Activity {
private static final String TAG = "FragmentOne";
private FragmentOne mFOne;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
Log.e(TAG, savedInstanceState+"");
if(savedInstanceState == null) {
mFOne = new FragmentOne();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, mFOne, "ONE");
tx.commit();
}
}
}现在无论进行多次旋转都只会有一个Fragment实例在Activity中,现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持?
- 和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。
07.Fragment使用建议
- 关于使用Fragment操作的使用建议
- 如果Fragment视图被频繁的使用,或者一会要再次使用,建议使用show/hide方法,这样可以提升响应速度和性能。
- 如果Fragment占用大量资源,使用完成后,可以使用replace方法,这样可以及时的释放资源。
- 传递参数建议
- Fragment的数据传递通过setArguments/getArguments进行,这样在Activity重启时,系统会帮你保存数据,这点和Activity很相似。
08.FragmentAdapter选择
FragmentPageAdapter和FragmentPageStateAdapter的区别?
- FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
- FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存
如何使用FragmentPageStateAdapter?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class BasePagerStateAdapter extends FragmentStatePagerAdapter {
private List<?> mFragment;
private List<String> mTitleList;
/**
* 接收首页传递的标题
*/
public BasePagerStateAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) {
super(fm);
this.mFragment = mFragment;
this.mTitleList = mTitleList;
}
@Override
public Fragment getItem(int position) {
return (Fragment) mFragment.get(position);
}
@Override
public int getCount() {
return mFragment==null ? 0 : mFragment.size();
}
/**
* 首页显示title,每日推荐等..
* 若有问题,移到对应单独页面
*/
@Override
public CharSequence getPageTitle(int position) {
if (mTitleList != null) {
return mTitleList.get(position);
} else {
return "";
}
}
}如何使用FragmentPagerAdapter?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65public class BasePagerAdapter extends FragmentPagerAdapter {
private List<?> mFragment;
private List<String> mTitleList;
/**
* 普通,主页使用
*/
public BasePagerAdapter(FragmentManager fm, List<?> mFragment) {
super(fm);
this.mFragment = mFragment;
}
/**
* 接收首页传递的标题
*/
public BasePagerAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) {
super(fm);
this.mFragment = mFragment;
this.mTitleList = mTitleList;
}
@Override
public Fragment getItem(int position) {
return (Fragment) mFragment.get(position);
}
@Override
public int getCount() {
return mFragment==null ? 0 : mFragment.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
/**
* 首页显示title,每日推荐等..
* 若有问题,移到对应单独页面
*/
@Override
public CharSequence getPageTitle(int position) {
if (mTitleList != null) {
return mTitleList.get(position);
} else {
return "";
}
}
public void addFragmentList(List<?> fragment) {
this.mFragment.clear();
this.mFragment = null;
this.mFragment = fragment;
notifyDataSetChanged();
}
}
//具体使用
FragmentManager supportFragmentManager = getSupportFragmentManager();
BasePagerAdapter myAdapter = new BasePagerAdapter(supportFragmentManager,
mFragments, mTitleList);
vpContent.setAdapter(myAdapter);
tabLayout.setupWithViewPager(vpContent);