四大组件/Fragment

引入

引入整个support-v4库,则
compile 'com.android.support:support-v4:24.2.1'
如果只想引入support-fragment库,则
com.android.support:support-fragment:24.2.1
Fragment是依赖于Activity的,不能独立存在的。

  • 一个Activity里可以有多个Fragment。
  • 一个Fragment可以被多个Activity重用。
  • Fragment有自己的生命周期,并能接收输入事件。
  • 我们能在Activity运行时动态地添加或删除Fragment。

Fragment 核心类

  1. Fragment:Fragment的基类,任何创建的Fragment都需要继承该类.
  2. FragmentManager:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl.
  3. FragmentTransaction:对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。
  4. Fragment有很多可以复写的方法,其中最常用的就是onCreateView(),该方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛如下异常:
1
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

ViewPager+Fragment相关

  1. 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中的FragmentPagerAdapter和FragmentStatePagerAdapter。

  1. FragmentPagerAdapter和FragmentStatePagerAdapter需要重写的方法都一样,常见的重写方法如下:
  • public FragmentPagerAdapter(FragmentManager fm): 构造函数,参数为FragmentManager。如果是嵌套Fragment场景,子 PagerAdapter的参数传入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。

懒加载

  1. 懒加载主要用于ViewPager且每页是Fragment的情况,场景为微信主界面,底部有4个tab,当滑到另一个tab时,先显示”正在加载”,过一会才会显示正常界面。
  2. 默认情况,ViewPager会缓存当前页和左右相邻的界面。实现懒加载的主要原因是:用户没进入的界面需要有一系列的网络、数据库等耗资源、耗时的操作,预先做这些数据加载是不必要的。
  3. 这里懒加载的实现思路是:用户不可见的界面,只初始化UI,但是不会做任何数据加载。等滑到该页,才会异步做数据加载并更新UI。
  4. 这里就实现类似微信那种效果,整个UI布局为:底部用PagerBottomTabStrip(https://github.com/tyzlmjj/PagerBottomTabStrip)项目实现,上面是ViewPager,使用FragmentPagerAdapter。逻辑为:当用户滑到另一个界面,首先会显示正在加载,等数据加载完毕后(这里用睡眠1秒钟代替)显示正常界面。
  5. ViewPager默认缓存左右相邻界面,为了避免不必要的重新数据加载(重复调用onCreateView()),因为有4个tab,因此将离线缓存的半径设置为3,即setOffscreenPageLimit(3)。
  6. 懒加载主要依赖Fragment的setUserVisibleHint(boolean isVisible)方法,当Fragment变为可见时,会调用setUserVisibleHint(true);当Fragment变为不可见时,会调用setUserVisibleHint(false),且该方法调用时机:
  • onAttach()之前,调用setUserVisibleHint(false)。
  • onCreateView()之前,如果该界面为当前页,则调用setUserVisibleHint(true),否则调用setUserVisibleHint(false)。
  • 界面变为可见时,调用setUserVisibleHint(true)。
  • 界面变为不可见时,调用setUserVisibleHint(false)。
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
public class LazyFragment extends Fragment { private View mRootView;
private boolean mIsInited;
private boolean mIsPrepared;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_lazy, container, false);
mIsPrepared = true;
lazyLoad();
return mRootView;
}
public void lazyLoad() {
if (getUserVisibleHint() && mIsPrepared && !mIsInited) {
//异步初始化,在初始化后显示正常UI
loadData();
}
}
private void loadData() {
new Thread() {
public void run() {
//1. 加载数据
//2. 更新UI
//3. mIsInited = true
}
}.start();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
lazyLoad();
}
}
public static LazyFragment newInstance() {
return new LazyFragment();
}
}

在Fragment中有两个变量控制是否需要做数据加载:

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