ViewPager是v4支持库中的一个控件,相信几乎所有接触Android开发的人都对它不陌生。之所以还要在这里翻旧账,是因为我在最近的项目中有多个需求用到了它,觉得自己对它的认识不够深刻。我计划从最简单的使用场景出发,记录我到目前为止所对ViewPager的使用情况以及有关它的一些知识点。

这个系列的代码将存放在Github仓库中,每篇文章对应一个分支。

这是第一篇文章,讲述关于ViewPager展示动态数据的方法与相关知识点。ViewPager展示动态数据的方法有好几种,汇总到一起都离不开PagerAdapter的一个方法getItemPosition。下面讨论一下具体方法。

错误的示例

上一篇文章介绍了如何展示静态数据。代码中的自定义的PagerAdapter中有一个setTexts方法,它的内容是这样的:

public synchronized void setTexts(List<String> texts) {
this.texts.clear();
if (texts != null && texts.size() > 0) {
this.texts.addAll(texts);
}
notifyDataSetChanged();
}

它的作用就是清除适配器列表中原有的数据,然后把外部传递进来的数据复制进列表,最后通知适配器更新。

外部数据变更时,直接调用该方法:

adapter.setTexts(randomData());  

看起来好像很合理,毕竟ListView等组件的适配器就是这么用的。

但是如果你真的运行起来就会发现ViewPager的展示的数据并不是如你所愿的更新了。数据变多时,前面的数据不更新;数据变少时,在页面展示最后一项的情况下还可以向左滑动看到部分旧数据;甚至出现白屏的情况。

代码见Github

那怎么正确更新?

使用ViewPager.setAdapter切换数据源

这是最简单的修改数据的方法,适合在整个数据源都发生变化的场景下使用。

在初始化ViewPager的使用我们使用下面的代码:

adapter = new DynamicDataSetAdapter();
adapter.setTexts(randomData()); viewPager = (ViewPager) findViewById(R.id.vp_viewpager_update);
viewPager.setAdapter(adapter);

当需要让ViewPager展示的数据改变时,我们可以:

// 可以选择创建新的PagerAdapter对象或使用已有的对象
// adapter = new DynamicDataSetAdapter();
adapter.setTexts(randomData());
viewPager.setAdapter(adapter);

代码见Github

为什么使用notifyDatasetChanged无法正确更新数据,而setAdapter可以?这要求我们了解一下ViewPager的更新原理。

ViewPager的更新原理

看ViewPager的源码会发现它拥有两个成员变量,分别是:

PagerAdapter mAdapter;
private PagerObserver mObserver;

PagerObserver是在ViewPager内部定义的私有类,也就是说它默认持有了ViewPager的引用,因此可以调用ViewPager的方法。

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

其实看PagerObserver的名字就知道这是一个观察者。PagerAdapter以PagerObserver为通道告知ViewPager调用dataSetChanged方法更新数据。

看dataSetChanged方法的源码,关键在:

...
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object); if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
} ...
}
...

我们看到dataSetChanged方法调用了PagerAdapter的getItemPosition方法了。一旦该方法返回了 PagerAdapter.POSITION_UNCHANGED就不刷新这个页面了。

由于我们使用了默认的getItemPosition方法,而默认的getItemPosition方法的实现恰好就返回了这个值:

public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}

到这里我们就明白了为什么修改数据后只调用notifyDataSetChanged不会刷新页面了。

接下来看一下setAdapter方法的源码。关键在于该方法的前面几行:

if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
...

可以看到一旦就的数据适配器不为null,ViewPager就会销毁所有原来与数据相关的ItemInfo,并且逐一调用适配器的destroyItem方法销毁视图。

setAdapter之后的代码就不用看了,跟初始化流程一样生成ItemInfo列表与相关数据。

这就是为什么调用setAdapter可以更新数据的原因。

重写getItemPosition实现更高效的数据更新

既然知道了getItemPosition决定了数据更新的规则,我们只要重写这个方法就可以了。

最暴力的方法当然是直接让这个方法返回POSITION_NONE,在效果上这跟使用setAdapter没什么区别了,只要调用了PagerAdapter的notifyDatasetChanged就会导致销毁原有的数据并重建。

更稳妥一点的方法是配合应用的实际业务数据进行该方法的定制。下面提供一个仅供参考的例子:

private static class DynamicDataSetAdapter extends PagerAdapter {
private List<String> texts; public DynamicDataSetAdapter() {
texts = new ArrayList<>();
} @Override
public int getCount() {
return texts.size();
} @Override
public boolean isViewFromObject(View view, Object object) {
return object.equals(view);
} @Override
public Object instantiateItem(ViewGroup container, int position) {
String text = texts.get(position); TextView textView = new TextView(container.getContext());
textView.setTag(text);
textView.setText(text); container.addView(textView);
return textView;
} @Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
} @Override
public int getItemPosition(Object object) {
View view = (View) object;
String text = (String) view.getTag();
if (text == null) {
return PagerAdapter.POSITION_NONE;
} int index = this.texts.indexOf(text);
if (index == -1) {
return PagerAdapter.POSITION_NONE;
} return index;
} public synchronized void setTexts(List<String> texts) {
this.texts.clear();
if (texts != null && texts.size() > 0) {
this.texts.addAll(texts);
}
notifyDataSetChanged();
}
}

代码见Github

本文来自作者同步博客

ViewPager使用记录2——展示动态数据的更多相关文章

  1. ViewPager使用记录1——展示固定数据

    ViewPager是v4支持库中的一个控件,相信几乎所有接触Android开发的人都对它不陌生.之所以还要在这里翻旧账,是因为我在最近的项目中有多个需求用到了它,觉得自己对它的认识不够深刻.我计划从最 ...

  2. Echarts基于动态数据初步使用 及问题 代码记录.

    ECHARTS 插件 基本的动态数据展示(横向图) 下载 echarts.commn.min.js文件 在页面中进行引用, 并为Echarts图形准备一个div盒子 <!-- 引入插件 --&g ...

  3. Echarts 展示两条动态数据曲线

    利用Echarts 展示两条动态数据曲线,每1秒刷新一下数据,在echart官网例子基础上修改,修改了仿真数据的生成方式.生成数量,曲线数量,最总效果图如下: 详细代码如下: 遇到的主要问题点, 1, ...

  4. C# ADO.NET动态数据的增删改查(第五天)

    一.插入登录框中用户输入的动态数据 /// <summary> /// 添加数据 /// </summary> /// <param name="sender& ...

  5. 【Paddy】如何将物理表分割成动态数据表与静态数据表

    前言 一般来说,物理表的增.删.改.查都受到数据量的制约,进而影响了性能. 很多情况下,你所负责的业务关键表中,每日变动的数据库与不变动的数据量比较,相差非常大. 这里我们将变动的数据称为动态数据,不 ...

  6. 用JSON-server模拟REST API(二) 动态数据

    用JSON-server模拟REST API(二) 动态数据 上一篇演示了如何安装并运行 json server , 在这里将使用第三方库让模拟的数据更加丰满和实用. 目录: 使用动态数据 为什么选择 ...

  7. PBOC2.0安全系列之—脱机认证之动态数据认证(DDA)

    动态数据认证: 一,什么是动态数据认证(DDA) 由于上篇<< PBOC2.0安全系列之—脱机认证之静态数据认证(SDA)>>已经对静态数据认证部分做了详细的分析,一些基本知识 ...

  8. JMeter接口测试实战-动态数据验证

    JMeter接口测试实战-动态数据验证 说到验证就不得不说断言, 先来看下JMeter官方给出断言(Assertion)的定义, 用于检查测试中得到的响应数据等是否符合预期,用以保证测试过程中的数据交 ...

  9. Saiku控制页面展示的数据过长自动换行(二十四)

    Saiku控制页面展示的数据过长自动换行 目前用到saiku来展示数据,发现数据文本过长也不会自动换行,然而用户那边又需要换行(会好看些),所以就来改一改源码啦 首先我们使用谷歌浏览器 inspect ...

随机推荐

  1. 201521123036 《Java程序设计》第13周学习总结

    本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 书面作业 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分 ...

  2. 201521123014 《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 数据库是为了实现一定目的按某种规则组织起来的"数据"的"集合".常见的数 ...

  3. Tiled Editor 图块的两种导入方式

    一.图块集图块的导入. 打开或者创建地图后,新建 新图块. 弹出新图块面板 图块类型选择 "基于图块集图块",一定要选择"嵌入地图",否则需要另存为其他类型的文 ...

  4. 如何查看maven plugin所包含的goal

    maven项目的构建生命周期(build lifecycle)由很多阶段组成:从validate到deploy. maven插件中包含goal.这些goal可以被绑定到不同的maven的构建阶段上.g ...

  5. pig报错

    pig failed to read data from....... 错误可能1:load data的目录不在,或者引用出错,load data '/in/train'这里的红色/应该去掉,因为默认 ...

  6. AngularJS的运用

      前  言 JRedu AngularJS[1]  诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多款产品当中.A ...

  7. Prison Break

    Prison Break 时间限制: 1 Sec  内存限制: 128 MB提交: 105  解决: 16[提交][状态][讨论版] 题目描述 Scofild又要策划一次越狱行动,和上次一样,他已经掌 ...

  8. SpringBoot初体验

    1.elipse中创建Springboot项目并启动 具体创建步骤请参考:Eclipse中创建新的Spring Boot项目 2.项目的属性配置 a.首先我们在项目的resources目录下appli ...

  9. H264 NAL解析

    NAL全称Network Abstract Layer,即网络抽象层.在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL).其中,前者负责有 ...

  10. 悟透JavaScript(二)

    初看原型 prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义.JavaScript中的prototype概念恰如其分地反映了这个词的内含,我们不能将其理 ...