Fragment整理

基本概念

Fragment,简称碎片,是Android3.0(API 11)提出的,为了兼容低版本,support-V4库中也开发了一套Fragment API,最低兼容Android 1.6

  • Fragment是依赖于Activity的,不能独立存在的
  • 一个Activity中可以有多个Fragment
  • 一个Fragment可以被多个Activity重用
  • Fragment有自己的声明周期,并能接收输入事件
  • 能在Activity运行时动态地添加或删除Fragment

Fragment的优势:

  • 模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中
  • 可重用(Reusability):多个Activity可以重用一个Fragment
  • 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好

基本使用:

  1. 继承Fragment
  2. 重写OnCreateView方法

注意:

  1. 传参时使用setArguments(Bundle bundle)方式可以在被系统杀死并恢复时保留数据,在onAttach()中通过getArguments()获得传进来的参数
  2. 如要使用Activity,不建议使用getActivity(),而是在onAttach()中将Context强转为Activity对象

如果使用getActivity(),稍有不慎会发生以下问题:

    1. Activity 的实例销毁问题。比如,Fragment 中存在类似网络请求之类的异步耗时任务,当该任务执行完毕回调 Fragment 的方法并用到宿主 Activity 对象时,很有可能宿主 Activity 对象已经销毁,从而引发 NullPointException 等异常,甚至造成程序崩溃。所以,异步回调时需要注意添加空值等判断(譬如:fragment.isAdd(),getActivity()!=null 等),或者在 Fragment 创建实例时就通过 getActivity().getApplicationContext() 方法保存整个应用的上下文对象,再来使用
    2. 内存泄漏问题。如果 Fragment 持有宿主 Activity 的引用,会导致宿主 Activity 无法回收,造成内存泄漏。所以,如果可以的话,尽量不要在 Fragment 中持有宿主 Activity 的引用

添加方式:

  • 静态添加:通过xml的方式添加,确定是一旦添加就不能在运行时删除
  • 动态添加:运行时添加,这种方式比较灵活,因此建议使用这种方式

静态添加:

  • 在Activity的布局文件中加入存放Fragment的容器,一般为FrameLayout
  • 通过代码添加进Activity
    • getSupportFragmentManager().beginTransaction().add(int containerViewId,Fragment fragment,String tag).commit();
    • 这里需要注意几点
      • 因为我们使用了support库的Fragment,因此需要使用getSupportFragmentManager()获取FragmentManager
      • add()是对Fragment众多操作中的一种,还有remove(),replace()等,第一个采纳数是根容器的id(FrameLayout的id),第二个参数是Fragment对象,第三个参数是Fragment的tag名,指定tag的好处是后续可以通过Fragment Fragment = getSupportFragmentManager().findFragmentByTag(String tag)从FragmentManager中查找Fragment对象
      • 在一次事务中,可以做多个操作,比如同时做add(),remove(),replace()
      • commit()操作是异步的,内部通过mManager.enqueueAction()加入处理队列。对应的同步方法为commitNow(),commit()内部会有checkStateLoss()操作,如果开发人员使用不当(比如commit()操作在onSaveInstanceState()之后),可能会抛出异常,而commitAllowingStateLoss()方法则是不会抛出异常版本的commit方法,但是尽量使用commit(),而不要使用commitAllowStateLoss() 为什么?因为在添加的时候不会判断当前activity是否是在执行onSaveInstanceState()之前,如果是在这个方法之后调用commitAllowStateLoss() 方法就会出现状态保留不到的情况,展示上就是显示不出来要添加的fragment,commit()会在添加的时候进行判断看实在什么状态
      • addToBackStack(String name)是可选的。FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity
      • Fragment有一个常见的问题,即Fragment重叠问题,这是由于Fragment被系统杀掉,并重新初始化时再次将fragment加入activity,因此通过在外围加if语句能判断此时是否是被系统杀掉并重新初始化的情况

Fragment重叠问题

一般发生在系统内存紧张,Activity需要销毁重建时,当Activity重新执行onCreate()方法时,已经存在的Fragment实例也会进行销毁重建,这会与Activity中onCreate()方法里面的第二次创建的Fragment同时显示从而发生UI重叠问题

处理方式有三种:

  1. 在Activity提供的onAttachFragment()方法中处理:

@Override

public void onAttachFragment(Fragment fragment) {

super.onAttachFragment(fragment);

if (fragment instanceof OneFragment){

oneFragment = (OneFragment) fragment;

}

}

  1. 在创建Fragment前添加判断,判断是否已经存在:

Fragment tempFragment = getSupportFragmentManager().findFragmentByTag("OneFragment");

if (tempFragment==null) {

oneFragment = OneFragment.newInstance();

ft.add(R.id.fl_content, oneFragment, "OneFragment");

}else {

oneFragment = (OneFragment) tempFragment;

}

  1. 最为简单的方法,在Activity的onCreate()方法使用saveInstanceState判断:

if (savedInstanceState==null) {

oneFragment = OneFragment.newInstance();

ft.add(R.id.fl_content, oneFragment, "OneFragment");

}else {

oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment");

}

Fragment onActivityResult()方法

此方法与Activity一样,但是不会进行二级分发,即只有最外层fragment才会分发到,如果想让fragment中的子fragment也能接收到,需要手动添加代码进行分发

状态变迁监听

监听fragment是否可见:onHiddenChanged()方法,此方法的变化直接影响isHidden()方法的返回值

除了isHidden()方法,还有一个isVisible()方法,也用于判断Fragment的状态,表明Fragment是否对用户可见,如果为true,必须满足三点条件:

  1. Fragment已经被add至Activity中
  2. 试图内容已经被关联到window上
  3. 没有被隐藏,即isHidden()为false

注意:onHiddenChanged()方法可以监听hide()和show()操作,与setUserVisibleHint()方法有所不同,后者常见的出现场景是在ViewPager和Fragment组合的FragmentpagerAdapter中使用。ViewPager滑动时便是通过这个方法改变Fragment的状态,利用这个方法可以实现Fragment懒加载,后文会对懒加载进行讲解

Fragment状态的持久化

  1. 使用Activity的onSaveInstanceState(Bundle outState) 状态保存

protected void onRestoreInstanceState(Bundle savedInstanceState)状态恢复

  1. 让Android自动帮我们保存fragment状态
    1. 我们只需要将Fragment在Activity中作为一个变量整个保存,只要保存了Fragment,那么Fragment的状态就得到了保存,所以我们就可以通过下面的方法,进行获取Fragment数据
      1. FragmentManager.putFragment(Bundle bundle,String key,Fragment fragment)是在Activity中保存Fragment的方法

FragmentManager.getFragment(Bundle bundle,String key)是在Activity中获取所保存的Fragment的方法

      1. 上述i仅仅能保存Fragment中的控件状态,比如说:EditText中用户已经输入的文字(注意:在这里,控件需要设置一个id值,否则Android将不会为我们保存该控件的状态),而Fragment中需要持久化的变量依然会丢失,但依然有解决办法,就是利用方法一
      2. 状态的持久化实例代码:

/** Activity中的代码 **/

FragmentB fragmentB;

 

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.fragment_activity);

if( savedInstanceState != null ){

fragmentB = (FragmentB) getSupportFragmentManager()

.getFragment(savedInstanceState,"fragmentB");

}

init();

}

 

@Override

protected void onSaveInstanceState(Bundle outState) {

if( fragmentB != null ){

getSupportFragmentManager()

.putFragment(outState,"fragmentB",fragmentB);

}

super.onSaveInstanceState(outState);

}

 

/** Fragment中保存变量的代码 **/

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable

ViewGroup container, @Nullable Bundle savedInstanceState) {

AppLog.e("onCreateView");

if ( null != savedInstanceState ){

String savedString = savedInstanceState

.getString("string");

//得到保存下来的string

}

View root = inflater.inflate(R.layout.fragment_a,null);

return root;

}

 

@Override

public void onSaveInstanceState(Bundle outState) {

outState.putString("string","anAngryAnt");

super.onSaveInstanceState(outState);

}

管理Fragment回退栈

跟踪回退栈的状态

我们通过实现OnBackStackChangedListener接口来实现回退栈跟踪,具体代码如下:

  1. 创建实现OnBackStackChangedListener的类
  2. 调用FragmentManager.addOnBackStackChangedListener(this)将新建的类传入

添加没有界面的Fragment

可以使用无界面的fragment为activity提供后台动作,可以使用add(Fragment,String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID).这样添加了fragment,但是,因为没有关联到activity布局中的视图,收不到onCreateView()的调用,所以不需要重写此方法。对于无界面fragment,字符串标签是唯一标识它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()

注意:

  1. 调用commit()并不立刻执行事务,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话可以从主线程调用executyePendingTransations()立即执行commit()
  2. 只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出异常。着是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss()

 

生命周期

Fragment的生命周期和Activity类似,但比Activity的生命周期更复杂一些,基本的生命周期方法如下图

解释如下:

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数
  • onCreate():Fragment被创建时调用
  • onCreateView():创建Fragment的布局
  • onActivityCreated():当Activity完成onCreate()时调用
  • onStart():当Fragment可见时调用
  • onResume():当Fragment可见且可交互时调用
  • onPause():当Fragment不可交互但可见时调用
  • onStop():当Fragment不可见时调用
  • onDestroyView():当Fragment的UI从视图结构中移除时调用
  • onDestroy():销毁Fragment时调用
  • onDetach():当Fragment和Activity解除关联时调用

上面的方法中,只有onCreateView()在重写时不用谢super方法,其他都需要

Fragment生命周期和Activity生命周期的关系:

 

FragmentTransaction一些基本方法调用时,Fragment生命周期的变化:

  • add(): onAttach()...-->onResume()
  • remove():onPause()...-->onDetach()
  • replace():相当于旧Fragment调用remove(),新Fragment调用add()
  • show():不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true
  • hide():不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false
  • detach():onPause()->onStop()->onDestroyView().UI从布局中移除,但是仍然被FragmentManager管理
  • attach():onCreateView()->onStart()->onResume()

Fragment实现原理和Back Stack

默认情况下,Fragment事务是不会加入回退栈的,如果想将Fragment事务加入回退栈,则可以加入addToBackStack(String name)。如果没有加入回退栈,则用户点击返回按钮会直接将Activity出栈,如果加入了回退栈,则用户点击返回按钮会回滚Fragment事务。

getSupportFragmentManager().beginTransaction() .add(R.id.container, f1, "f1") .addToBackStack("") .commit();

 

上面的代码的功能是将Fragment加入Activity中,内部实现为:创建一个BackStackRecord对象,该对象记录了这个事务的全部操作轨迹,然后将该对象提交到FragmentManager的执行队列中,等待执行

 

BackStackRecord类的定义如下

class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl。OpGenerator {}

从定义可以看出,BackStackRecord有三重含义:

  • 继承了FragmentTransaction,即是事务,保存了整个事务的全部操作轨迹
  • 实现了BackStackEntry,作为回退栈的元素,正是因为该类拥有事务全部的操作轨迹,因此在popBackStack()时能回退整个事务
  • 实现了OpGenerator,即被放入FragmentManagerImpl执行队列,等待被执行

BackStackRecord

BackStackRecord类包含了一次事务的整个操作轨迹,是以集合形式存在的,集合的元素是Op类,表示其中某个操作,定义如下:

static final class Op {

int cmd; //操作是add或remove或replace或hide或show等

Fragment fragment;//对哪个Fragment对象做操作

int enterAnim;//进入动画

int exitAnim;//退出动画

int popEnterAnim;

int popExitAnim;

 

Op() {

}

 

Op(int cmd, Fragment fragment) {

this.cmd = cmd;

this.fragment = fragment;

}

}

我们来看下具体场景下这些了类是怎么被使用的,比如我们的事务做add操作,add函数定义如下

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {

doAddOp(containerViewId, fragment, tag, OP_ADD);

return this;

}

doAddOp()方法定义:

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {

final Class fragmentClass = fragment.getClass();

final int modifiers = fragmentClass.getModifiers();

if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)

|| (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {

throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()

+ " must be a public static class to be properly recreated from"

+ " instance state.");

}

 

fragment.mFragmentManager = mManager;

 

if (tag != null) {

if (fragment.mTag != null && !tag.equals(fragment.mTag)) {

throw new IllegalStateException("Can't change tag of fragment "

+ fragment + ": was " + fragment.mTag

+ " now " + tag);

}

fragment.mTag = tag;

}

 

if (containerViewId != 0) {

if (containerViewId == View.NO_ID) {

throw new IllegalArgumentException("Can't add fragment "

+ fragment + " with tag " + tag + " to container view with no id");

}

if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {

throw new IllegalStateException("Can't change container ID of fragment "

+ fragment + ": was " + fragment.mFragmentId

+ " now " + containerViewId);

}

fragment.mContainerId = fragment.mFragmentId = containerViewId;

}

 

addOp(new Op(opcmd, fragment));

}

 

  1. 检查fragment类的修饰符并将FragmentManagerImpl赋值给fragment的mFragmentManager
  2. 检查传入的tag是否不为null且与fragment已有的tag不一致,如果不一致则抛出异常,否则将tag赋值给fragment的mTag(注意:此处tag可为null,且不会抛出异常而会把fragment的mTag也赋值为null)
  3. 检查传入的containerViewId是否为0,如果为0则执行addOp(),不为0,则检查containerViewId是否没有Id,没有Id则抛出异常,都符合的情况下,进行fragment的mFragmentId检查,如果不为0且与传入的containerViewId不相等,则抛出异常
  4. 执行addOp(),创建Op对象,并加入集合

 

addOp()是将创建好的Op对象加入链表,并进行动画赋值,定义如下:

 

void addOp(Op op) {

mOps.add(op);

op.enterAnim = mEnterAnim;

op.exitAnim = mExitAnim;

op.popEnterAnim = mPopEnterAnim;

op.popExitAnim = mPopExitAnim;

}

 

addToBackStack()是将mAddToBackStack置为true,并给mName赋值。在commit()中会用到mAddToBackStack变量。commit()是异步的,即不是立即生效的,但是后面会看到整个过程还是在主线程完成,只是把事务的执行扔给主线程的Handler,commit()内部是commitInternal(),实现如下:

int commitInternal(boolean allowStateLoss) {

if (mCommitted) throw new IllegalStateException("commit already called");

if (FragmentManagerImpl.DEBUG) {

Log.v(TAG, "Commit: " + this);

LogWriter logw = new LogWriter(TAG);

PrintWriter pw = new PrintWriter(logw);

dump(" ", null, pw, null);

pw.close();

}

mCommitted = true;

if (mAddToBackStack) {

mIndex = mManager.allocBackStackIndex(this);

} else {

mIndex = -1;

}

mManager.enqueueAction(this, allowStateLoss);//将事务添加到执行集合

return mIndex;

}

如果mAddToBackStack为true,则调用allocBackStackIndex(this)将事务添加进回退栈,FragmentManager类的变量mBackStackIndices就是回退栈。实现如下:

public int allocBackStackIndex(BackStackRecord bse) {

synchronized (this) {

if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {

if (mBackStackIndices == null) {

mBackStackIndices = new ArrayList<BackStackRecord>();

}

int index = mBackStackIndices.size();

if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);

mBackStackIndices.add(bse);

return index;

 

} else {

int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);

if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);

mBackStackIndices.set(index, bse);

return index;

}

}

}

在commitInternal()中,mManager.enqueueAction(this, allowStateLoss),是将BackStackRecord加入待执行队列中,定义如下:

public void enqueueAction(OpGenerator action, boolean allowStateLoss) {

if (!allowStateLoss) {

checkStateLoss();

}

synchronized (this) {

if (mDestroyed || mHost == null) {

if (allowStateLoss) {

// This FragmentManager isn't attached, so drop the entire transaction.

return;

}

throw new IllegalStateException("Activity has been destroyed");

}

if (mPendingActions == null) {

mPendingActions = new ArrayList<>();

}

mPendingActions.add(action);

scheduleCommit();

}

}

mPendingActions是前面说的待执行队列

scheduleCommit()的定义如下:

private void scheduleCommit() {

synchronized (this) {

boolean postponeReady =

mPostponedTransactions != null && !mPostponedTransactions.isEmpty();

boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;

if (postponeReady || pendingReady) {

mHost.getHandler().removeCallbacks(mExecCommit);

mHost.getHandler().post(mExecCommit);

}

}

}

mHost.getHandler()就是主线程的Handler,因此Runnable是在主线程执行的,mExecCommit的内部就是调用了execPendingActions(),即把mPendingActions中所有积压的没被执行的事务全部执行。执行队列中的事务会怎样被执行呢?就是调用BackStackRecord的runOnCommitRunnables()方法调用mCommitRunnables中的所有Runnable的run()方法,run()方法就是执行Fragment的生命周期函数,还有将视图添加进container中

 

与addToBackStack()对应的是popBackStack(),有以下几种变种:

  • popBackStack():将回退栈的栈顶弹出,并回退该事务
  • popBackStack(String name,int flag):name为addToBackStack(String name)的参数,通过name能找到回退栈的特定元素,flag可以为0或者FragmentManager.POP_BACK_STACK_INCLUSIVE,0表示只弹出该元素以上的所有元素,POP_BACK_STACK_INCLUSIVE表示弹出包含该元素及以上的所有元素。这里说的弹出所有元素包含回退这些事务
  • popBackStack()是异步执行的,是丢到主线程的MessageQueue执行,popBackStackImmediate()是同步版本

补充一点:getSupportFragmentManager().findFragmentByTag()是经常用到的方法,他是FragmentManager的方法,FragmentManager是抽象类,FragmentManagerImpl是继承FragmentManager的实现类,他的内部实现是:

public Fragment findFragmentByTag(String tag) {

if (tag != null) {

// First look through added fragments.

for (int i=mAdded.size()-1; i>=0; i--) {

Fragment f = mAdded.get(i);

if (f != null && tag.equals(f.mTag)) {

return f;

}

}

}

if (mActive != null && tag != null) {

// Now for any known fragment.

for (int i=mActive.size()-1; i>=0; i--) {

Fragment f = mActive.valueAt(i);

if (f != null && tag.equals(f.mTag)) {

return f;

}

}

}

return null;

}

从上面看到,先从mAdded中查找是否有该Fragment,如果没找到,再从mActivity中查找是否有该Fragment。mAdded是已经添加到Activity的Fragment的集合,mActive不近包含mAdded,还包含虽然不在Activity中,但还在回退栈中的Fragment

 

Fragment通信

通常,Fragment 与 Activity 通信存在三种情形:Activity 操作内嵌的 Fragment,Fragment 操作宿主 Activity,Fragment 操作同属 Activity中的其他 Fragment。

由于 Activity 持有所有内嵌的 Fragment 对象实例(创建实例时保存的 Fragment 对象,或者通过 FragmentManager 类提供的 findFragmentById() 和 findFragmentByTag() 方法也能获取到 Fragment 对象),所以可以直接操作 Fragment;Fragment 通过 getActivity() 方法可以获取到宿主 Activity 对象(强制转换类型即可),进而可以操作宿主 Activity;那么很自然的,获取到宿主 Activity 对象的 Fragment 便可以操作其他 Fragment 对象。

推荐,使用对外开放接口的形式将 Fragment 的一些对外操作传递给宿主 Activity

Fragment向Activity传递数据

  1. 使用接口进行传递
    1. 首先,在Fragment中定义接口,并让Activity实现该接口
    2. 在Fragment的onAttach()中,将参数Context强转为接口对象
    3. 在Fragment合适的地方调用接口方法从而传递数据
  2. FABridge

由于通过接口的方式从Fragment向Activity进行数据传递比较麻烦,需要在Fragment中定义interface,并让Activity实现该interface,FABridge通过注解的形式免去了这些定义。

    1. 在build.gradle中添加依赖:
    2. 定义方法ID,这里为FAB_ITEM_CLICK,在Activity中定义接口:

@FCallbackId(id = FAB_ITEM_CLICK)

public void onItemClick(String str) { //方法名任意

Toast.makeText(this, str, Toast.LENGTH_SHORT).show();

}

 

    1. 最后,在Fragment中,通过以下形式调用“ID=FAB_ITEM_CLICK”的方法(该方法可能在Activity中,也可能在任何类中):

Fabridge.call(mActivity,FAB_ITEM_CLICK,"data"); //调用ID对应的方法,"data"为参数值

Activity向Fragment传递数据

Activity向Fragment传递数据比较简单,获取Fragment对象,并调用Fragment的方法即可,比如要将一个字符串传递给Fragment,则在Fragment中定义方法,在Activity中调用即可

Fragment之间通信

由于Fragment之间是没有任何依赖关系的,因此如果要进行Fragment之间的通信,建议通过Activity作为中介,不要Fragment之间直接通信

DialogFragment

DialogFragment是Android 3.0提出的,代替了Dialog,用于实现对话框。他的优点是:即使旋转屏幕,也能保留对话框状态

如果要自定义对话框样式,只需要继承DialogFragment,并重写onCreateView(),该方法返回对话框UI。这里我们举个例子,实现进度条样式的圆角对话框。

关键代码实现为

DialogFragment fragment = new DialogFragment(); fragment.show(getSupportFragmentManager(), "tag");

 

ViewPager+Fragment相关

基本使用

ViewPager是support v4库中提供界面滑动的类,继承自ViewGroup。PagerAdapter是ViewPager的适配器类,为ViewPager提供界面。但是一般来说,通常都会使用 PagerAdapter的两个子类:FragmentPagerAdapter和FragmentStatePagerAdapter作为ViewPager的适配器,他们的特点是界面是Fragment。

在support v13和support v4中都提供了fragmentPagerAdapter和FragmentStatePagerAdapter,区别在于:support v13中使用android.app.fragment,而support v4使用android.support.v4.app.fragment。一般都使用support v4中的。

默认,VIewPager会缓存当前页相邻的界面,比如当滑动到第2页时,会初始化第1页和第3页的界面(即Fragment对象,且生命周期函数运行到onResume()),可以通过setOffscreenPageLimit(count)设置离线缓存的界面个数

FragmentPagerAdapter和FragmentStatePagerAdapter需要重写的方法都一样,常见的重写方法如下:

  • public FragmentPagerAdapter(FragmentManager fm):构造函数,参数为FragmentManager。如果是嵌套Fragment场景,子PagerAdapter的参数传入Fragment.getChildFragmentManager()。
  • Fragment getItem(int position):返回地position位置的Fragment,必须重写
  • int getCount():返回ViewPager的页数,必须重写
  • Object instantiateItem(ViewGroup container,int position):container是ViewPager对象,返回第position位置的Fragment
  • void destroyItem(ViewGroup container,int position,Object object):container是ViewPager对象,object是Fragment对象
  • getItemPosition(Object object):object是Fragment对象,如果返回POSITION_UNCHANGED,则表示当前Fragment不刷新,如果返回POSITION_NONE,则表示当前Fragment需要调用destroyItem()和instantiateItem()进行销毁和重建。默认情况下返回POSITION_UNCHANGED

懒加载

懒加载主要用于ViewPager且每页是Fragment的情况,场景为微信主界面,底部有4个tab,当滑到另一个tab时,先显示“正在加载”,过一会才会显示正常界面

默认情况,ViewPager会缓存当前页和左右相邻的界面,实现懒加载的主要原因是:用户没进入的界面需要一系列的网络、数据库等耗资源、耗时的操作,预先做这些数据加载是不必要的。

这里懒加载的实现思路是:用户不可见的界面,只初始化UI,但是不会做任何数据加载。等滑到该页,才会异步做数据加载并更新UI

这里实现类似微信的那种效果,整个UI布局为:底部用PagerBottomTabStrip项目实现,上面是ViewPager,使用FragmentPagerAdapter。逻辑为:当用户滑到另一个界面,首先会显示正在加载,等加载完毕后(这里用睡眠1秒钟代替)显示正常界面。

ViewPager默认缓存左右相邻界面,为了避免不必要的重新数据加载(重复调用onCreateView()),因为4个tab,因此将离线缓存的半径设置为3,即setOffscreenPageLimit(3)

懒加载主要依赖Fragment的setUserVisibleHint(boolean isVisible)方法,当Fragment变为可见时,会调用setUserVisibleHint(true);当Fragment变为不可见时,会调用setUserVisibleHint(false),且该方法调用时机:

  • onAttach()之前,调用setUserVisibleHint(false)
  • onCreateView()之前,如果该界面为当前页,则调用setUserVisibleHint(true),否则调用setUserVisibleHint(false)
  • 界面变为可见时,调用setUserVisibleHint(true),界面变为不可见时,调用setUserVisibleHint(false)

懒加载Fragment的实现:

abstract class LazyFragment : Fragment() {

private var mRootView: View? = null

private var mIsInited = false

private var mIsPrepared = false

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

mRootView = getCreateView()

mIsPrepared = true

lazyLoad()

return mRootView

}

 

abstract fun getCreateView(): View

 

public fun lazyLoad() {

if (userVisibleHint && mIsPrepared && !mIsInited) {

//异步初始化数据,初始化后显示正常UI

loadData()

}

}

 

private fun loadData() {

thread {

//1.加载数据

//2.更新UI

mIsInited = true

}.start()

}

 

override fun setUserVisibleHint(isVisibleToUser: Boolean) {

super.setUserVisibleHint(isVisibleToUser)

if (isVisibleToUser) {

lazyLoad()

}

}

}

注意点:

  • 在Fragment中有两个变量控制是否需要做数据加载:
    • mIsPrepared:表示UI是否已准备好,因为数据加载后需要更新UI,如果UI还没有inflate,就不需要做数据加载,因为setUserVisibleHint()会在onCreateView()之前调用一次,如果此时调用,UI还没有inflate,因此不能加载数据
    • mIsInited:表示是否已经做过数据加载,如果做过了就不需要做了。因为setuserVisibleHInt(true)在界面可见时都会调用,如果滑到该界面做过数据加载后,滑走,再滑回来,还是会调用setUserVisibleHint(true),此时由于mIsInited=true,因此不会再做一遍数据加载
  • lazyLoad():懒加载的核心方法,再该方法中,只有界面可见(getUserVisibleHint()==true)、UI准备好(mIsPrepared==true)、过去没做过数据加载(mIsInited==true)时,才需要调loadData()做数据加载,数据加载做完后把mIsInited置为true

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值