本章介绍了如何使用 ViewPager(准确地说,应该是使用了 FragmentStatePagerAdapter 的简单的 ViewPager)。
GitHub 地址:
完成第十一章
1. ViewPager 和 PagerAdapter
ViewPager 在某种程度上类似于 RecyclerView,它们都需要借助于 Adapter 来支持,ViewPager 需要的是 PagerAdapter。
ViewPager 与 PagerAdapter 之间的配合比 RecyclerView 与其 Adapter 之间复杂得多。但是对于本章而言,因为使用的是 PagerAdapter 的子类 FragmentStatePagerAdapter
,它能协助处理很多细节问题.
FragmentStatePagerAdapter
化繁为简,提供了两个有用的方法:getCount()
和 getItem (int)
。
调用 getCount()
方法顾名思义就是获取数据集的大小。调用 getItem(int)
方法,返回的是应该是和数据绑定的Fragment,一般来说会将其和数据集的位置相对应。
使用步骤:
- 布局文件,使用 ViewPager(因为只有支持包而没有内置,所以不像 fragment 需要选择)
- 在代码中声明 ViewPager 变量并引用
- 本书中使用的是匿名 FragmentStatePagerAdapter 类,在其中直接重写了两个关键方法,然后就可以使用了。
// 引用 ViewPager
mViewPager = (ViewPager) findViewById(R.id.activity_crime_pager_view_pager);
// 获取数据集
mCrimes = CrimeLab.get(this).getCrimes();
// 获取 FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
// 使用匿名内部类来引用 FragmentStatePagerAdapter,构造方法的参数是 FragmentManager
mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
@Override
public Fragment getItem(int position) {
Crime crime = mCrimes.get(position);
return CrimeFragment.newInstance(crime.getId());
}
@Override
public int getCount() {
return mCrimes.size();
}
});
// 设置完 Adapter 以后,就要选择当前的数据啦~这就是上一个 Activity 传递进来的数据,我就不予赘述了。
for (int i = 0; i < mCrimes.size(); i++) {
if (mCrimes.get(i).getId().equals(crimeId)) {
mViewPager.setCurrentItem(i);
break;
}
}
2. FragmentStatePagerAdapter 与 FragmentPagerAdapter
FragmentPagerAdapter
是另外一种可用的 PagerAdapter , 其用法与 FragmentStatePagerAdapter
基本一致。唯一的区别在于:卸载不再需要的 fragment 时,各自采用的处理方法有所不同。
FragmentStatePagerAdapter
会销毁不需要的 fragment。事务提交后,activity 的 FragmentManager
中的 fragment 会被彻底移除。FragmentStatePagerAdapter
类名中的“state”表明:在销毁 fragment 时,可在 onSaveInstanceState(Bundle)
方法中保存 fragment 的 Bundle 信息。用户切换回来时,保存的实例状态可用来恢复生成新的fragment。
相比之下,FragmentPagerAdapter
有不同的做法。对于不再需要的 fragment,FragmentPagerAdapter
会选择调用事务的 detach(Fragment) 方法来处理它,而非 remove(Fragment) 方 法。也就是说,FragmentPagerAdapter
只是销毁了 fragment 的视图,fragment 实例还保留在 FragmentManager
中。因此,FragmentPagerAdapter
创建的 fragment 永远不会被销毁。
选择哪种adapter取决于应用的要求。通常来说,使用 FragmentStatePagerAdapter
更节省内存。CriminalIntent 应用需显示大量crime记录,每份记录最终还会包含图片。在内存中保存所有信息显然不合适,因此我们选择使用 FragmentStatePagerAdapter
。
另一方面,如果用户界面只需要少量固定的fragment,则 FragmentPagerAdapter
是个安全、 合适的选择。
最常见的例子为分页显示用户界面。例如,某些应用的明细视图所含内容较多,通 常需分两页显示。这时就可以将这些明细信息分拆开来,以多页面的形式展现。显然,为用户界面添加支持滑动切换的 ViewPager,能增强应用的触摸体验。此外,将 fragment 保存在内存中,更易于管理控制层的代码。对于这种类型的用户界面,每个 activity 通常只有两三个 fragment,基本不用担心有内存不足的风险。
3. 深入学习:ViewPager 的工作原理
什么时候需要自己实现PagerAdapter接口呢?需要ViewPager托管非 fragment 视图时,就需要实现原生 PagerAdapter 接口。
PagerAdapter 要比 RecyclerView 的 Adapter复杂得多,因为它要处理更多的视图管理工作。
PagerAdapter 不使用可返回视图的onBindViewHolder(...)
方法,而是使用下列方法:
public Object instantiateItem(ViewGroup container, int position)
public void destroyItem(ViewGroup container, int position, Object object)
public abstract boolean isViewFromObject(View view, Object object)
PagerAdapter.instantiateItem(ViewGroup, int)
方法告诉 PagerAdapter 创建指定位置的列表项视图,然后将其添加给 ViewGroup 视图容器,而destroyItem(ViewGroup, int, Object)
方法则告诉 PagerAdapter 销毁已建视图。(注意,instantiateItem(ViewGroup, int)
方法并不要求立即创建视图。因此,PagerAdapter 可自行决定何时创建视图。)视图创建完成后,ViewPager 会在某个时间点注意到它。为确定该视图所属的对象,ViewPager 会调用
isViewFromObject(View, Object)
方法。这 里 , Object 参数是instantiateItem(ViewGroup,int)
方法返回的对象。因此,假设 ViewPager 调用instantiateItem(ViewGroup, 5)
方法返回一个 A 对象,那么只要传入的 View 参数是第5个对象的视图,isViewFromObject(View, A)
方法就应返回true值,否则返回false值。
对 ViewPager 来说,这是一个复杂的过程,但对于PagerAdapter来说,这算不上什么。因为PagerAdapter只要能够创建、销毁视图以及识别视图来自哪个对象即可。这样的要求显然很宽松,因而PagerAdapter 能够比较自由地通过 instantiateItem(ViewGroup, int)
方法创建并添加新的fragment ,然后返回可以跟踪管理的 Object(fragment) 。
以下为isViewFromObject (View, Object)方法的具体实现:
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
可以看到,每次需要使用ViewPager时,都要覆盖实现PagerAdapter的这些方法,这真是一种磨难。所幸我们有 FragmentPagerAdapter
和 FragmentStatePagerAdapter
这么便利的类。
GitHub Page: kniost.github.io
简书:http://www.jianshu.com/u/723da691aa42