Android技能树 — Fragment整体小结

前言:

Android基础知识java

Android技能树 — Fragment整体小结react

Android技能树 — 动画小结android

Android技能树 — View小结ios

Android技能树 — Activity小结segmentfault

Android技能树 — View事件体系小结bash

Android技能树 — Android存储路径及IO操做小结网络

Android技能树 — 多进程相关小结app

Android技能树 — Drawable小结ide

Android技能树 — 屏幕适配小结函数


好久没有写文章了,没其余缘由,就是由于懒。

由于最近的APP开发,使用的是单Activity + 多Fragment的方式,不一样于之前基本界面都是Activity的方式,因此Fragment用了不少,想到本身之前也写了不少相关的基础知识,Fragment却历来没有写过,因此就打算补上一篇fragment的基础总结。

老样子,先上脑图:

咱们就按照脑图的顺序同样样来看Fragment的基础知识。


正文:

1.Fragment的添加

咱们知道Fragment是一个"碎片(或者片断)",添加在Activity中。若是我如今问你,Activity要显示一个按钮Button,你会怎么作?

1. 直接在Layout.xml中添加<Button/>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />
</LinearLayout>
复制代码

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"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
复制代码

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中获取Context

咱们能够直接在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<Fragment> list;

    //经过构造获取fragment集合
    public Fragment_pager(FragmentManager fm,ArrayList<Fragment> list) {
        super(fm);
        this.list=list;
    }
    //设置具体position的fragment
    @Override
    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<Permission>() {
        @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<Permission> 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)方式来传递参数

6.2 其余数据传递方式

Activity 与 Fragment 数据传递:

Fragment 与 Fragment 数据传递

重点说下setTargetFragment,由于不少人都不知道。

咱们的目标:FragmentA 启动FragmentB ,而后FragmentB作完事情,返回结果给FragmentA

FragmentB.setTargetFragment(FragmentA);

而后在B中:
getTargetFragment().onActivityResult(getTargetRequestCode(), resultOK, i);


而后再FragmentA中:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

复制代码

7. Fragment重建恢复数据

推荐下面这篇文章:

[译] 保存/恢复 Activity 和 Fragment 状态的最佳实践

引用一段话:

彻底分开处理Fragment状态和view状态 为了使你的代码变得干净和可扩展,你最好把Fragment状态和View状态分开处理。若是这里有任何属性是属于View的,在View内部进行保存和恢复.若是这里有任何属性是属于Fragment的,在Fragment内部进行保存和恢复。


8.经常使用监听Fragment显示方法

这块比较基础,就不细讲了。


9.监听Fragment发生变化

回退栈(back stack)状态改变监听:

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
    
    }
});
复制代码

注册fragment的生命监听:

List<Fragment> fragmentList = new ArrayList<>();

getActivity().getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
    @Override
    public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, @Nullable Bundle savedInstanceState) {
        super.onFragmentViewCreated(fm, f, v, savedInstanceState);
        fragmentList.add(f);
    }

    @Override
    public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
        super.onFragmentViewDestroyed(fm, f);
        fragmentList.remove(f);
    }
}, false);

//'这里咱们注意下最后的代码里面的那个false,这个false的意思是:不递归碎片中的碎片了,就是碎片栈中的碎片'

复制代码

10. DialogFragment:

咱们知道如今你们已经不多使用了Dialog类,而是使用了DialogFragment,其本质就是个Fragment。

其实这个原本也想多写点,可是我估计这个基本安卓开发都使用过,因此就直接用网上其余做者的基础介绍文章:

Android 必知必会 - DialogFragment 使用总结

同时具体的自定义DialogFragment我之前文章也有写过:

项目需求讨论-仿ios底部弹框实现及分析


结语:

好久没写文章了。一看竟然都快半年了......后面准备慢慢的补起本身的博客。有错误的地方欢迎你们指出

相关文章
相关标签/搜索