Android技能树 — Fragment总体小结


2. 在代码中动态添加,比如我们添加到一个LinearLayout中:

Button button = new Button(mActivity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
button.setLayoutParams(params);
container.addView(button);

所以Fragment也很简单,就把它当做一个简单的View(但其实更像是“子 Activity”),然后添加方式也是一样。

1. 直接在Layout.xml中添加:

<?xml version="1.0" encoding="utf-8"?>

2. 直接在代码中添加:

Fragment one = new FragmentOne();//自定义的Fragment类
//要先获取FragmentManager对象
FragmentManager fragmentManager = getSupportFragmentManager();
//开启一个FragmentTransaction事务
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.framelayout_view,one).commit();

其中添加到R.id.framelayout_view的这个idViewGourp可以是<FrameLayout/>,也可以是其他的比如<LinearLayout/>等。


2. Fragment的基本操作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看到我们上面的动态代码添加的时候需要获取FragmentTransactionFragmentManager

2.1 FragmentManager相关

1. getFragmentManager():

获取Fragment父容器的管理器,但是现在该方法在Activity中已经被标记不推荐使用了。

/**

  • Return the FragmentManager for interacting with fragments associated
  • with this activity.
  • @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
    */
    @Deprecated
    public FragmentManager getFragmentManager() {
    return mFragments.getFragmentManager();
    }

2. getSupportFragmentManager():

v4包下的这个方法,与上一个效果一样,不过是Android推荐使用的方法(毕竟可以兼容Android所有版本)

3. getChildFragmentManager():

我们提过,Fragment更像是一个“子Activity”,那你说"子Activity"中能否再添加Fragment,答案当然是可以。那么在Fragment内部中的Fragment的管理器,就需要使用getChildFragmentManager()来获取了。

2.2 FragmentTransaction相关

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到有添加删除各种方法操作。

1. attach/detach方法:

  • detach(Fragment fragment) : 分离指定Fragment的UI视图
  • attach(Fragment fragment) : 重新关联一个Fragment(当这个Fragment的detach执行之后)
  1. 当Fragment被detach后,Fragment的生命周期执行完onDestroyView就终止了,这意味着Fragment的实例并没有被销毁,只是UI界面被移除了(注意和remove是有区别的)。
  2. 当Fragment被detach后,执行attach操作,会让Fragment从onCreateView开始执行,一直执行到onResume。
  3. attach无法像add一样单独使用,单独使用会抛异常。方法存在的意义是对detach后的Fragment进行界面恢复。

2.add/remove方法:

我想这二个是用的最多的了,add()和remove()是将fragment添加和移除. remove()比detach()要彻底一些, 如果不加入到回退栈中, remove()的时候, fragment的生命周期会一直走到onDetach();如果加入了回退栈,则会只执行到onDestoryView(),Fragment对象还是存在的。

add一个fragment,如果加到的是同一个id的话,有点像我们的Activity栈,启动多个Activity时候,Activity一个个叠在上面,fragment也是类似,一个个fragment叠在上面。

3.replace方法:

replace = remove + add , 所以可以理解为先把相同id下的Fragment移除掉,然后再加入这个当前的fragment。

所以如果你觉得Fragment存在太多,影响性能,可以用replace来切换各个界面,就可以保证当前只有一个Fragment,但是因为每次切换后,Fragment都会重建,所以如果这个界面有网络请求相关的,你就会发现这个界面又重新去请求网络接口了,显得很多此一举。

4.hide/show方法:

就是字面意思,让一个Fragment隐藏,让一个Fragment显示。你可以理解为Button设置了View.GONE和View.VISIBLE。常常配合有多个Fragment及有TAB等切换方式的时候,选中某个按钮,然后根据相应的让对应的Fragment显示,其他Fragment隐藏。

5.commit/commitAllowingStateLoss:

我估计很多人认识这个commitAllowingStateLoss大部分是因为自己的代码有闪退异常:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

在你离开当前Activity等情况下,系统会调用onSaveInstanceState()帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()之前),你执行Fragment事务,就会抛出该异常。然后网上有很多教程,叫你提交的时候使用commitAllowingStateLoss()方法,虽然说不会抛出错误,但是如果在Activity已经保存状态完之后提交了它,到时候Ativity意外崩溃,再恢复数据的时候就不会恢复在Activity保存状态之后提交的fragment的更新,造成状态丢失了。

额外补充:
1.commit()方法并不立即执行transaction中包含的动作,而是把它加入到UI线程队列中. 如果想要立即执行,可以在commit之后立即调用FragmentManager的executePendingTransactions()方法.

2. commit()方法必须在状态存储之前调用,否则会抛出异常,如果觉得状态丢失没关系, 可以调用commitAllowingStateLoss(). 但是除非万不得已, 一般不推荐用这个方法, 会掩盖很多错误.

6. addToBackStack:

我们可以看到FragmentTransaction里面有加入回退栈方法,但是没有退出的方法:popBackStack。这是因为这个方法在FragmentManager里面。

也就是如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一般反应是,addToBackStack和popBackStack不是应该像上面的类似add和remove一样,都一个层级的吗??所以popBackStack不也应该是FragmentTransaction下的一个方法???

所以我们单从图片所示就能知道,popBackStackFragmentTransaction是一个层级,所以popBackStack操作的其实也是《fragment事务》(FragmentTransaction),所以可以理解为addToBackStack把我们前面的FragmentTransaction事务(比如add,remove,replace等一系列操作)加入到了回退栈(!!!记住不是把fragment加入到了回退栈),而popBackStack是操作回退栈里面的事务。

当然具体的源码过程分析,细讲的话又是很多,都可以另外专门写一篇文章,所以直接借鉴网上别人已经写好的文章:

Fragment那点事①Fragment栈管理

额外补充:
1.加入回退栈:remove掉的fragment执行onDestoryView,并没有执行onDestory,fragment实例对象还是存在,当回退时候,fragment从onCreateView处执行

2. 未加入回退栈:remove掉的fragment 执行 onDestoryView和onDestory,彻底销毁移除


3.Fragment中获取Co外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

资料获取→专栏
ntext

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以直接在fragment代码里面直接使用getActivity()getContext()方法。

但是有时候获取为空,所以一般我们使用的是:

Class xxxFragment extends Fragment {

private Context mContext;

//‘高版本后,都是回调这个方法’
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}

//‘API低于 23 的版本的时候,是会回调这个方法’
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
}
}


4.Fragment配合ViewPager

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ViewPager配合Fragment的时候,主要使用FragmentPagerAdapterFragmentStatePagerAdapter这二个Adapter。其实使用很简单(一般的最最简单的写法):

public class FragmentAdapter extends FragmentPagerAdapter{
private ArrayList list;

//通过构造获取fragment集合
public Fragment_pager(FragmentManager fm,ArrayList list) {
super(fm);
this.list=list;
}
//设置具体position的fragment
@Overri
de
public Fragment getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
//设置有多少个fragment
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
}

然后ViewPager.setAdapter(xxxx);

但是大家会奇怪为啥有二个Adapter:FragmentPagerAdapterFragmentStatePagerAdapter,他们的区别我们可以看具体的源码:

FragmentPagerAdapter源码:

public abstract class FragmentPagerAdapter extends PagerAdapter {

//‘初始化创建Item:’
@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}

long itemId = this.getItemId(position);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
if (fragment != null) {

//‘后面使用fragment是通过FragmentTransaction.attach方式加进来的,’
//‘只是重新绘制了UI,fragment对象还在。’
this.mCurTransaction.attach(fragment);
} else {

//‘我们知道刚返回fragment使用的是getItem(position)方法’
//‘我们可以看到第一次使用fragment是通过FragmentTransaction.add方式加进来的’
fragment = this.getItem(position);
this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
}

if (fragment != this.mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}

return fragment;
}

//‘销毁item:’
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}

//‘我们可以看到FragmentTransaction只是单纯的detach了fragment,视图不在了,但是fragment对象还在’
this.mCurTransaction.detach((Fragment)object);
}

}

我们可以看到fragment并没有真的销毁,FragmentPageAdapter则适用于固定的,少量的Fragment情况,例如和TabLayout共同使用时。

FragmentStatePagerAdapter源码:

public abstract class FragmentStatePagerAdapter extends PagerAdapter {

@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Fragment fragment;
if (this.mFragments.size() > position) {
fragment = (Fragment)this.mFragments.get(position);
if (fragment != null) {
return fragment;
}
}

if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}

fragment = this.getItem(position);
if (this.mSavedState.size() > position) {
SavedState fss = (SavedState)this.mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}

while(this.mFragments.size() <= position) {
this.mFragments.add((Object)null);
}

fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
this.mFragments.set(position, fragment);

//‘我们可以看到fragment都是add进来的’
this.mCurTransaction.add(container.getId(), fragment);
return fragment;
}

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}

while(this.mSavedState.size() <= position) {
this.mSavedState.add((Object)null);
}

this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
this.mFragments.set(position, (Object)null);

//‘可以看到都是通过remove的方式移除了’
this.mCurTransaction.remove(fragment);
}

}

所以我们知道了FragmentStatePagerAdapter是真的会把fragment对象都销毁,所以如果fragment数量很多的话,使用这个会更好,因为fragment存在太多,对应用性能造成很大影响,所以要remove掉fragment。


5.无UI的fragment:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.1 使用Fragment 保持需要恢复对象

调用setRetainInstance(true)方法可保留fragment,如下:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);

}

比如旋转屏幕,已保留的fragment不会随着activity一起被销毁(但会销毁fragment的视图); 相反,它会一直保留(进程不消亡的前提下),并在需要时原封不动地传递给新的Activity。

所以我们比如一些对象可以保持在fragment中,这时候Activity重新恢复后,其他对象可以从fragment中找回。

可以大概看下其他作者文章介绍:

Fragment调用setRetainInstance的原理

5.2 类似RxPermission用于处理回调

RxPermission里有一个Fragment用于分发权限回调。这个是什么意思??

我们知道原生请求权限:

//发出权限请求:
int requestCode = 1;
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE},requestCode);

//权限处理结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

是不是觉得在要看复写这个回调方法很麻烦???而且没有美感。

而RxPermission是这样申请权限的:

RxPermissions rxPermissions = new RxPermissions(this);

rxPermissions.requestEach(
//请求的权限
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE)
.subscribe(new Consumer() {
@Override
public void accept(@io.reactivex.annotations.NonNull Permission permission) throws Exception {
//权限通知回调
}
});

感觉就是一步呵成的感觉,很棒。但是RxPermission只是对系统的原生权限申请做了封装而已,那系统的原本的回调函数:onRequestPermissionsResult去哪里了呢???

public class RxPermissionsFragment extends Fragment {



//‘申请权限’
@TargetApi(23)
void requestPermissions(@NonNull String[] permissions) {
this.requestPermissions(permissions, 42);
}

//‘申请权限后结果回调’
@TargetApi(23)
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 42) {
boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];

for(int i = 0; i < permissions.length; ++i) {
shouldShowRequestPermissionRationale[i] = this.shouldShowRequestPermissionRationale(permissions[i]);
}

this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
}
}

//‘回调后的具体处理方法’
void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
int i = 0;

for(int size = permissions.length; i < size; ++i) {
this.log("onRequestPermissionsResult " + permissions[i]);
PublishSubject subject = (PublishSubject)this.mSubjects.get(permissions[i]);
if (subject == null) {
Log.e(RxPermissions.TAG, “RxPermissions.onRequestPermissionsResult invoked but didn’t find the corresponding permission request.”);
return;
}

this.mSubjects.remove(permissions[i]);
boolean granted = grantResults[i] == 0;

//‘subject主动调用onNext方法发送结果’
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
subject.onComplete();
}

}



}

我们可以到这个fragment内部已经帮我们复写了请求权限的原生方法和权限回调通知的原生方法。然后再通过subject在结果处发送通知即可。

这里我不会细讲整个RxPermission源码,我以前写过的相关文章,大家可以具体看下:

项目需求讨论 - 动态权限申请分析及相关第三方库源码分析

项目需求讨论 — 手把手带你写RxPermission


6.构造函数和数据传递

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.1 构造函数传递数据

我们知道fragment也就是普通的对象,可以通过new的方式,我们平常使用对象传递值都是可以直接在构造函数里面定义参数值,直接赋值进去,那fragment是否可以这样??答案是可以的,但是不推荐。

public class FragmentOne extends Fragment {

//‘在其他地方直接FragmentOne one = new FragmentOne(“青蛙要fly”);进行值传递’
//‘但是我们不推荐这样’
public FragmentOne(String value) {

}

//‘而是通过bundle来传递,Fragment.setArguments(Bundle)赋值进去’
public static FragmentOne newInstance(Bundle args) {
FragmentOne fragment = new FragmentOne();
if(args != null){
fragment.setArguments(args);
}
return fragment;
}

}

原因:我们可以知道Activity重新创建时,会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失(因为当切换横竖屏时,Fragment会调用自己的无参构造函数,那么在构造函数传参就会失效),但是通过 Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来,从而数据又可以恢复,所以尽量使用 Fragment.setArguments(Bundle bundle)方式来传递参数
这样’
public FragmentOne(String value) {

}

//‘而是通过bundle来传递,Fragment.setArguments(Bundle)赋值进去’
public static FragmentOne newInstance(Bundle args) {
FragmentOne fragment = new FragmentOne();
if(args != null){
fragment.setArguments(args);
}
return fragment;
}

}

原因:我们可以知道Activity重新创建时,会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失(因为当切换横竖屏时,Fragment会调用自己的无参构造函数,那么在构造函数传参就会失效),但是通过 Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来,从而数据又可以恢复,所以尽量使用 Fragment.setArguments(Bundle bundle)方式来传递参数

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值