Android 开发时使用 ViewPager 的问题及解决方案整理
1. ViewPager 的页面重置问题
当我们使用ViewPager控件时,假设我们的ViewPager有三页,当我们第一次启动ViewPager显示第一页的时候,ViewPager会预加载第二页,这样当我们向第二页滑动的时候就可以看见第二页的内容了,因为第二页的内容在第一页内容显示的时候就已经加载了。当我们滑动到第二页时,ViewPager会预加载第三页,这时ViewPager中已经保存了三页的内容:第一页,第二页和第三页,这样当我们在第二页向左或向右滑动ViewPager时能看到这三页的内容。
我在使用ViewPager控件时遇到过这样的问题,像上面说明的那样,我在把ViewPager从第一页滑动到第三页的时候看到了这三页的内容,但是假设我在第一页中做了一些操作改变了第一页的内容,而我在第三页中也做了一些操作改变了第三页的内容,可是当我从第三页滑动到第一页时发现第一页的内容还原了,而当我再次从第一页滑动到第三页时发现第三页的内容也还原了。
问题的根源在于ViewPage的加载页数,就是说你可以指定ViewPager预加载的页数,如果不指定,ViewPager默认加载一页。在这种情况下,ViewPage会加载当前显示页面相邻的还没有加载的页面。比如说,当前显示的是首页,因为首页的左边是没有页面的,所以ViewPager会加载第二页的内容;当ViewPager滑动到第二页的时候,因为第一页已经加载,所以不会重复加载,但是第三页还没有加载,所以ViewPager会加载第三页的内容;当ViewPager滑动到第三页的时候(假设ViewPager只有三页),因为第二页已经加载过了,而第三页的右边没有任何页面了,所以ViewPager不会加载任何界面。当从第三页滑动到第二页时,因为第三页已经加载过,所以不会重新加载,虽然第一页之前加载过,但是,ViewPager的默认加载为一页,而当ViewPager在第三页的时候第一页在默认加载之外,所以从第三页滑动到第二页的时候,ViewPager会重新加载第一页,而再从第一页滑动到第三页时是同样的道理,所以会出现我上面描述的情况。
为了避免上述情况的发生我们可以通过调用ViewPager的setOffscreenPageLimit方法来指定加载的页数,从而得到我们想要的结果。
2. 调用setCurrentItem方法无动画问题
在使用setCurrentItem时,我们可以通过反射机制设置Viewpager的滑动过渡时间。
具体的实现方式如下:
/**
* ViewPager 滚动速度设置
*
*/
public class ViewPagerScroller extends Scroller {
private int mScrollDuration = 2000; // 滑动速度 /**
* 设置速度速度
* @param duration
*/
public void setScrollDuration(int duration){
this.mScrollDuration = duration;
} public ViewPagerScroller(Context context) {
super(context);
} public ViewPagerScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
} public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
} @Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mScrollDuration);
} @Override
public void startScroll(int startX, int startY, int dx, int dy) {
super.startScroll(startX, startY, dx, dy, mScrollDuration);
} public void initViewPagerScroll(ViewPager viewPager) {
try {
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
mScroller.set(viewPager, this);
} catch(Exception e) {
e.printStackTrace();
}
}
} //这个是设置viewPager切换过度时间的类
ViewPagerScroller scroller = new ViewPagerScroller(context);
scroller.setScrollDuration(0);
scroller.initViewPagerScroll(viewPager); //这个是设置切换过渡时间为0毫秒 ViewPagerScroller scroller = new ViewPagerScroller(context);
scroller.setScrollDuration(2000);
scroller.initViewPagerScroll(viewPager);//这个是设置切换过渡时间为2秒
如果需要设置其他类型的动画效果,可以调用 ViewPager.setPageTransformer 来设置。
3. ViewPager调用notifyDataSetChanged() 刷新问题解决方案
问题说明:
ViewPager控件很大程度上满足了开发者开发页面左右移动切换的功能,使用非常方便。但是使用中发现,在删除或者修改数据的时候,PagerAdapter无法像BaseAdapter那样仅通过notifyDataSetChanged方法通知刷新View。有人提出一种解决方案:给Viewpager重新设置一遍适配器adapter,来达到刷新数据的目的。但是这种方法在大多数情况下,是存在问题的。
问题分析:
为什么调用数据更新的方法,Viewpager却没有更新呢,我们跟进该方法的源代码看一下。
/**
* This method should be called by the application if the data backing this adapter has changed
* and associated views should update.
*/
public void notifyDataSetChanged() {
mObservable.notifyChanged();
}
注释里说到,当附加在适配器上的数据发生变化时,应该调用该方法刷新数据。该方法调用了一个mObservable .notifyChanged():
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* Called when the contents of the data set have changed. The recipient
* will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers ) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers .size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
这都不是重点,重点我们来看这个mObservers的类型是一个抽象类DataSetObserver,里面只有两个未实现的方法,都有谁使用了这个抽象类呢,快捷键 ctrl + alt + H ,在众多的调用者当中,我们发现了Viewpager的身影。进入Viewpager,我们终于找到了Viewpager中控制数据变更的重点方法dataSetChanged ,这个方法如下:
void dataSetChanged () {
// This method only gets called if our observer is attached, so mAdapter is non-null.
boolean needPopulate = mItems .size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < mAdapter.getCount();
int newCurrItem = mCurItem ;
boolean isUpdating = false;
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;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate( this);
isUpdating = true;
}
mAdapter.destroyItem( this, ii.position , ii.object);
needPopulate = true;
if (mCurItem == ii.position ) {
// Keep the current item in the valid range
newCurrItem = Math. max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem ) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii. position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate( this);
}
Collections. sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor ) {
lp. widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
重点看这样一行代码:
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue ;
}
到这里我们就找到了解决这个问题的核心方法:getItemPosition()。官方对getItemPosition()的解释是:
Called when the host view is attempting to determine if an item’s position has changed.
Returns POSITION_UNCHANGED if the position of the given item has not changed orPOSITION_NONE if the item is no longer present in the adapter.
The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.
意思是如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,而返回POSITION_UNCHANGED。
解决方案:
根据上面的分析,我们可以尝试着修改适配器的写法,覆盖getItemPosition()方法,当调用notifyDataSetChanged时,让getItemPosition方法人为的返回POSITION_NONE,从而达到强制Viewpager重绘所有item的目的。
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
参考资料:https://www.cnblogs.com/cheneasternsun/p/6017012.html
Android 开发时使用 ViewPager 的问题及解决方案整理的更多相关文章
- Android开发时,那些相见恨晚的工具或网站!
本文来我在知乎话题Android开发时你遇到过什么相见恨晚的工具或网站?下的回答! 在实际Android开发过程确实会有很多相见恨晚的工具或网站出现,下面是我自己的一些分享. 1.源码网站 https ...
- (转载) Android开发时,那些相见恨晚的工具或网站!
huangmindong的专栏 目录视图 摘要视图 订阅 赠书 | 异步2周年,技术图书免费选 程序员8月书讯 项目管理+代码托管+文档协作,开发更流畅 Android ...
- Android开发之利用ViewPager实现在Activity或Fragment中引入别的布局文件实现滑动并进行页面跳转
有些时候经常可以看到其他APP中有一排的图标,可以在一个界面中滑来滑去,并且图标可以进行点击事件进行页面的跳转.这里对这种方法的实现做出总结. 首先看一下图片: 下面这两种图片是在一个Fragment ...
- android开发学习之ViewPager滑动事件讲解
android ViewPager滑动事件讲解 今天在做项目的时候,由于要处理viewPager页面滑动的事件,所以对其进行了一个小小的研究: 首先ViewPager在处理滑动事件的时候要用到OnPa ...
- Android开发实战之ViewPager实现向导界面
当我们更新应用,或者第一次进入应用时都会有一个向导界面,介绍这个app的内容和使用方式. 如果你细心你会发现其实这就是个viewpager,本篇博文将介绍应用的向导界面是如何制作的.希 望本篇博文对你 ...
- Android开发实战之ViewPager的轮播
在安卓开发的许多控件中,如果你没有使用过ViewPager,就不能算是一个安卓开发工程师,在本篇博文中,我会总结ViewPager的使用方法, 以及一些开发中的拓展.希望本篇博文对你的学习和工作有所帮 ...
- Android开发时包名、签名、渠道和版本号的易坑点(转)
本文中总结一下 Android 开发中容易被忽视的一些注意事项吧: 一.谨慎选择包名 包名 (Package Name) 就相当于一款应用在户口本上登记的名字,是系统用来区分不同应用的字段.重复的包名 ...
- Android开发UI之ViewPager及PagerAdapter
ViewPager,官网链接--http://developer.android.com/reference/android/support/v4/view/ViewPager.html ViewPa ...
- 解决Mac上Android开发时adb连接不到手机问题
今天在Mac OS上进行Android开发的时候,打开eclipse连接不到手机MX4问题 1. 插入手机打开 Terminal,输入 system_profiler SPUSBDataType 2 ...
随机推荐
- 18.JAVA-pull解析XML
1.pull解析介绍 pull解析其实和我们上章学的sax解析原理非常类似,在Android中非常常用. 在java中,需自己获取xmlpull所依赖的类库分别为:kxml2-2.3.0.jar,xm ...
- 通过谷歌浏览器从官网下载Android Studio
谷歌访问助手安装教程参考:https://www.cnblogs.com/waiwai14/p/11697371.html 谷歌访问助手下载地址:https://pan.baidu.com/s/1YH ...
- android6.0系统Healthd深入分析
概述 Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信 ...
- spi-mem: 为SPI存储器生态带来一些一致性
在本文中,我们将介绍关于spi-mem Linux内核框架的工作,该框架将允许在SPI NOR设备和常规SPI设备以及SPI NAND设备上复用SPI控制器驱动程序. 从SPI到双线.四线.八线SPI ...
- VMware Workstation15激活码
VG5HH-D6E04-0889Y-QXZET-QGUC8 亲测可用
- Ocelot网关在.Net Core 的使用
1. 项目环境 .Net Core 2.2 Ocelot 13.5.2 2. 基本使用 * Nuget 安装 Ocelot , Ocelot.Provider.Polly * 修改 Pro ...
- HeadFirst设计模式<2>
HeadFirst设计模式<2> 1 装饰者模式 星巴克咖啡 饮料 总结 如果说策略模式是通过组合实现弹性,那么装饰者模式就是通过继承来实现,在实现的同时,客户基本感觉不到使用了装饰者模式 ...
- fiddler教程:抓包带锁的怎么办?HTTPS抓包介绍。
点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 介绍Fiddler的HTTPS抓包功能." 这里首先回答下标题中的疑问,fiddler抓包带锁的原因是HTTPS流量抓包功能开启, ...
- SSM(SpringMVC+Spring+MyBatis)三大框架使用Maven快速搭建整合(实现数据库数据到页面进行展示)
本文介绍使用SpringMVC+Spring+MyBatis三大框架使用Maven快速搭建一个demo,实现数据从数据库中查询返回到页面进行展示的过程. 技术选型:SpringMVC+Spring+M ...
- PHP switch的写法
switch switch (expression) { case label1: expression = label1 时执行的代码 ; break; case label2: expressio ...