ViewPager 作为展示一组页面的容器,在Android上被广泛使用,这边文章将围绕 ViewPager 如何显示页面展开,接口如何设计展开。

PagerAdapter 的接口设计

ViewPager 是与一组页面进行交互的容器,那么怎么设计交互的接口就成为设计成败的关键。我们会发现 ListView 中使用的「通信接口」是 BaseAdapter, 那么类似地,ViewPager 在设计的时候, 同样采用了 Adapter 的设计模式, 通过 PagerAdapter 来实现交互。

我们要达成的协议应该如下,ViewPager 负责显示页面,刷新页面,处理滑动等逻辑,而 PagerAdapter 负责实现如何渲染界面等具体接口。ViewPager 不直接操作页面,把这一切逻辑都放在 PagerAdapter 里面去,甚至页面复用这些逻辑也交由 PageAdapter 处理。那么我们来看看 PagerAdapter 是如何定义的?

PagerAdapter 提供了4种最基础的方法需要实现。

public Object instantiateItem(ViewGroup container, int position) {return instantiateItem((View) container, position);
}public void destroyItem(ViewGroup container, int position, Object object) {destroyItem((View) container, position, object);
}public abstract int getCount();public abstract boolean isViewFromObject(View view, Object object);

首先是 instantiateItem 方法,这个方法在指定的位置,和容器上面实例化 Page, 需要注意的是这些操作必须在 {@link #finishUpdate(ViewGroup)} 之前完成(这个会在后面解释)。 ViewPager 会在合适的时机调用这个方法来显示页面。

DestroyItem 这个方法与 instantiateItem 类似,用于销毁页面,在实现的时候,我们可以在这个时间来做一些缓存或者回收等一些事情。同样这些事情也必须在 {@link #finishUpdate(ViewGroup)} 保证执行结束。

getCount() 返回相应的数目

isViewFromObject(View view, Object object) 这个方法可以仔细说一下。首先看看 instantiateItem 的返回值,这里是 Object,读者可能有疑问了,为什么不是 Fragment 了? 虽然在大多数 Android 程序中,ViewPager 都是用来显示一系列的 Fragment ,但在设计的时候,我们就不能这么闭塞地思考问题。根据开闭原则,我们对扩展是开放的,因而我们除了可以显示一系列 Fragment 以外,还可以显示 View, 或者其他别的什么,所以这么返回值限定为 Object。 返回值和 View 并没有什么关系,ViewPager 只是用这个来标记这个 item 的,也就是建立起 item <–> object 之间的映射。在这样的情况下,能够做更多事情。

例如我们要实现一个显示水果的 ViewPager, 分别是 apple / banner / pear / peach / watermelon。起初第一个版本,我们使用 Fragment 来显示这些水果。

public enum Fruit {Apple, Banner, Pear, Peach, Watermelon
}
public Object instantiateItem(ViewGroup container, int position) {// new fragmentFruitFragment fragment = new FruitFragment();fragment.setArgument(fruits.getItem(position));fragmentTransaction.add(fragment);// return result.return fragment.
}public boolean isViewFromObject(View view, Object object) {return ((Fragment)object).getView() == view;
}

同样我们也可以通过显示 View 来替代 Fragment 的实现。

public Object instantiateItem(ViewGroup container, int position) {// new fragmentView view = ViewUtils.inflate(R.layout.fruit_item);view.setImageResource(fruit.getResId());// return result.return view.
}public boolean isViewFromObject(View view, Object object) {return ((View)object).getView() == view;
}

ViewPager 是如何与 PagerAdapter 进行沟通的

在前面的叙述中,ViewPager 是与 PagerAdapter 进行交互的,在具体实现中,ViewPager 在 PagerAdapter 里面注入了一个 Observer, 在 setAdapter(PagerAdapter adapter) 调用 mAdapter.registerDataSetObserver(mObserver);

private class PagerObserver extends DataSetObserver {@Overridepublic void onChanged() {dataSetChanged();}@Overridepublic void onInvalidated() {dataSetChanged();}
}

当PagerAdapter 中的数据发生变化时,PagerAdapter 调用 mObservable.notifyChanged(); 来通知 ViewPager 进行相应的处理。ViewPager 会收到相应的回调, 在 dataSetChanged() 方法中进行相应的处理。

FragmentPagerAdapter 与 FragmentStatePagerAdapter

Android System 针对大多数都是基于 Fragment 来进行页面展示的,因此实现了两个扩展类 FragmentPagerAdapter 与 FragmentStatePagerAdapter。 这两个类可以认为是对 PagerAdapter 进行了二次封装,实现了对 Fragment 的复用和管理。

在进行封装后,这两个类都只需要实现两个接口就可以 work 了(实际上,我们需要做的事情要远比这两个接口多)。

/*** Return the Fragment associated with a specified position.* 返回相应的Fragment*/
public abstract Fragment getItem(int position);/*** Return the number of views available.*/
public abstract int getCount();

先看看 FragmentPagerAdapter 是怎么实现的。 FragmentPagerAdapter 继承了 PagerAdapter ,实现了大部分方法,主要适用于静态页面和页面不太多的情况。页面一旦被渲染出来,就会被保存到 FragmentManager里面去,当页面重新出现的时候,就重新attach上去,这样效率会比较好。

@Override
public Object instantiateItem(ViewGroup container, int position) {// 在instantiate的时候,添加Fragment// 在 finishUpdate的时候,commit transaction.if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}final long itemId = getItemId(position);// Do we already have this fragment?String name = makeFragmentName(container.getId(), itemId);// fragment 是否已经存在了Fragment fragment = mFragmentManager.findFragmentByTag(name);if (fragment != null) {// 如果已经存在,就调用重新 attach 上// 需要注意的地方是,如果Fragment new出来后,在viewpager 没被销毁的时候,Fragment 就不会被释放掉// 当页面不在显示的时候,只是 detach from fragment manager.// 因此当我们在Fragment 查询到对应tag 的 Fragment 存在,就直接 attach 上就好。if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);mCurTransaction.attach(fragment);} else {// 实例化 Fragment,这个就需要自己实现了// 当Fragment实例化出来后,就添加的 fragment manager 里面去.fragment = getItem(position);if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);mCurTransaction.add(container.getId(), fragment,makeFragmentName(container.getId(), itemId));}if (fragment != mCurrentPrimaryItem) {fragment.setMenuVisibility(false);fragment.setUserVisibleHint(false);}return fragment;
}@Override
public void destroyItem(ViewGroup container, int position, Object object) {if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}// destroy的时候,并不销毁 Fragment,只是从detach掉if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object+ " v=" + ((Fragment)object).getView());mCurTransaction.detach((Fragment)object);
}@Override
public void finishUpdate(ViewGroup container) {if (mCurTransaction != null) {mCurTransaction.commitAllowingStateLoss();mCurTransaction = null;mFragmentManager.executePendingTransactions();}
}@Override
public boolean isViewFromObject(View view, Object object) {return ((Fragment)object).getView() == view;
}

FragmentStatePagerAdapter 与 FragmentPagerAdapter 类似,区别在于 FragmentStatePagerAdapter 更适合于对 Fragment 页面变化比较多,或者经常发生变动的情况。

@Override
public Object instantiateItem(ViewGroup container, int position) {// If we already have this item instantiated, there is nothing// to do.  This can happen when we are restoring the entire pager// from its saved state, where the fragment manager has already// taken care of restoring the fragments we previously had instantiated.// 如果 Fragment 存在,那么直接返回,不用add// 因为不可见的Fragment 都会被remove掉if (mFragments.size() > position) {Fragment f = mFragments.get(position);if (f != null) {return f;}}if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}Fragment fragment = getItem(position);if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);if (mSavedState.size() > position) {// 获取可能存在的状态Fragment.SavedState fss = mSavedState.get(position);if (fss != null) {fragment.setInitialSavedState(fss);}}while (mFragments.size() <= position) {mFragments.add(null);}fragment.setMenuVisibility(false);fragment.setUserVisibleHint(false);mFragments.set(position, fragment);mCurTransaction.add(container.getId(), fragment);return fragment;
}@Override
public void destroyItem(ViewGroup container, int position, Object object) {Fragment fragment = (Fragment)object;if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object+ " v=" + ((Fragment)object).getView());while (mSavedState.size() <= position) {mSavedState.add(null);}mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));mFragments.set(position, null);// 不缓存,直接移除掉,这样可以节省内存mCurTransaction.remove(fragment);
}

使用 PagerAdapter 的正确姿势

FragmentPagerAdapter 的使用细节

  • 根据前面所说的情况,getItem() 并不能被确保调用,因此 getItem() 在传递参数的时候,只适合传递一些静态的内容。如果我们在 getItem() 方法调用了一些需要动态改变的东西,然后使用 notifyDataSetChanged() ,会发现不起作用,就是因为这个缘故。如果需要在生成 Fragment 对象后,将数据集中的一些数据传递给该 Fragment,这部分代码应该放到这个函数的重载里。在我们继承的子类中,重载该函数,并调用 FragmentPagerAdapter.instantiateItem() 取得该函数返回 Fragment 对象,然后,我们该 Fragment 对象中对应的方法,将数据传递过去,然后返回该对象(可参考这里的实现Fragment 如何与Activity 进行交互)。
  • 注意在 getItem() 的时候不能重复调用 SetArguments() 方法,这种数据传递方式只可能用一次,在 Fragment 被添加到 FragmentManager 后,一旦被使用,我们再次调用 setArguments() 将会导致 java.lang.IllegalStateException: Fragment already active 异常。因而可以采用前面提及的方法。
  • 当显示的页面发生变化的时候,需要 getItemPosition() 进行特殊处理。getItemPosition() 方法有两个魔法值。这个方法会在 ViewPager 需要调用查看当前页面是否发生改变的时候调用, 默认返回 POSITION_UNCHANGED 表示页面没有发生改变,这也是我们常出现bug的地方。返回 POSITION_NONE 来表示页面已经不存在,或者我们可以返回新的位置。通常我们可以返回 POSITION_NONE 来强制进行页面的刷新。
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;

FragmentStatePagerAdapter 的使用细节

Fragment.setArguments()这种只会在新建 Fragment 时执行一次的参数传递代码,可以放在 getItem()里面,其余的代码应该放在 instantiateItem() 里面去。

viewpager 与 pageradapter相关推荐

  1. android viewpager 详解,详解Android App中ViewPager使用PagerAdapter的方法

    PageAdapter是一个抽象类,直接继承于Object,导入包android.support.v4.view.PagerAdapter即可使用. 要使用PagerAdapter, 首先要继承Pag ...

  2. 解决ViewPager和PagerAdapter中调用notifyDataSetChanged失效问题(从notifyDataSetChanged方法的源码入手,超详细)

    从PagerAdapter的notifyDataSetChanged方法源码入手解决ViewPager和PagerAdapter中调用notifyDataSetChanged失效的解决办法 1:问题描 ...

  3. android ViewPager之PagerAdapter中View的重用

    http://www.cnblogs.com/Theone2014/p/4748613.html http://blog.csdn.net/sunkeperfect/article/details/1 ...

  4. ViewPager——基础知识和PagerAdapter必须重写的四个方法

    一.效果展示 二.前期准备 1.给需要使用ViewPager的活动的布局中添加ViewPager控件 2.为你的ViewPager创建页面布局 这里我们创建3个背景色不同的布局 三.在java代码中实 ...

  5. ViewPager PagerAdapter未更新视图

    我正在使用兼容性库中的ViewPager. 我已经巧妙地让它显示了几个我可以翻阅的视图. 但是,我很难弄清楚如何使用一组新视图更新ViewPager. 我已经尝试了各种各样的事情,比如调用mAdapt ...

  6. Android ViewPager使用具体解释

    这是谷歌官方给我们提供的一个兼容低版本号安卓设备的软件包,里面包囊了仅仅有在安卓3.0以上能够使用的api.而viewpager就是当中之中的一个利用它,我们能够做非常多事情,从最简单的导航,到页面菜 ...

  7. ViewPager的缓存机制

    1.实现Viewpager的页面懒加载: 在某些情况下,例如使用ViewPager查看多张大图,此时多张图片不能一次性载入,只有在浏览该页面时才载入(或者预先载入下一页面)页面的具体内容. 2.可控V ...

  8. Android开发之ViewPager滑动页面效果实现(源代码分享)

    我们先来谷歌官方文档对viewpager的介绍,该类允许用户通过页面翻转左右的数据,需要通过实现PagerAdapter适配器来生成视图显示的页面.因为注意这个类是早期设计和开发的,API可能会改变, ...

  9. ViewPager 详解(三)---PagerTabStrip与PagerTitleStrip添加标题栏的异同

     相关文章: 1.<ViewPager 详解(一)---基本入门> 2.<ViewPager 详解(二)---详解四大函数> 3.<ViewPager 详解(三)-- ...

最新文章

  1. SSAS系列——【07】多维数据(查询Cube)
  2. 时间换算_只愿与一人十指紧扣_新浪博客
  3. 从安装Kafka服务到运行WordCount程序
  4. Spark Executor内幕
  5. Secure Delivery Center (SDC)功能概述
  6. MySQL 修复root权限
  7. JavaWeb(一)——web服务器、Tomcat安装和配置
  8. 第14章:傅里叶变换
  9. Apprentissage du français partie 1
  10. python不换行空格输出_解决Python print输出不换行没空格的问题
  11. openresty组成和技术特点
  12. 中国城市新分级名单(转)
  13. Windows下9001端口被占用
  14. wxpython 显示mdi界面_wxPython中Icon, MDI, HtmlWindow使用示例
  15. 如何用数学课件制作工具推导圆面积公式
  16. 【6.24校内test】T2 不老梦
  17. TDengine极简实战:从采集到入库,从前端到后端,体验物联网设备数据流转
  18. linux认证考试内容,Linux认证考试RHCE大纲
  19. 随笔-vue项目引入axios
  20. HMAC和NMAC 生日攻击

热门文章

  1. vue自定义指令directives同时传递多个参数
  2. 识图在线识图_三个图片无损放大在线工具分享,把模糊图片变清晰
  3. ICPR 2020 | 论文阅读 ——SyNet: An Ensemble Network for Object Detection in UAV Images
  4. 哔咔官网打不开显示黑屏?
  5. 基于单片机的频率测量控制系统设计 (频率计)(电路+程序)
  6. Python图像识别-Opencv05 色彩
  7. pfx 证书导出公钥和私钥
  8. 微博营销和软文营销的价值
  9. 我的DirectShow著作
  10. iphone html 手机震动,​苹果手机震动器在哪里?如何设置与关闭?