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 ...
随机推荐
- mac-安装sshpass
在配置了ssh免密认证id_rsa.pub之后,在cmd终端可以实现免密登陆对应配置了密钥的服务器,但是在python程序中,想要调用cmd执行免密操作,还需要安装sshpass,sshpass用于非 ...
- Java集合内容
Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括List,Set和Map.Java集合使用统一的Iterator遍历. 1.List遍历 实现了Iterator接口的 ...
- gitlab如何从Github导入项目
本文简单演示如何Github上导入项目到私人搭建的Gitlab中,搭建过程参考:CentOS7 搭建gitlab服务器. Gitlab版本是gitlab-ce-12.0.2,界面可能稍有差异,但应该影 ...
- 三个月前的评测拖延三个月仍旧是拳王No.1吗?YES!
距前作展开隐形的翅膀,WPR003N补完篇仿佛已经隔了几个光年,最近替换了2019发现android sdk需要手冻放入一个tools2文件夹来延续Eclipse style的m$ distribut ...
- centos7下安装客户端rabbitmq9.0
下载目前最新rabbitmq客户端版本: wget https://github.com/alanxz/rabbitmq-c/archive/v0.9.0.tar.gz php扩展 : wget ht ...
- Docker Compose 基本使用
Dockercompose v3官网文档: https://docs.docker.com/compose/compose-file/ Dockercompose中文: http://www.d ...
- QGIS练手 - 数据
又熬夜了... 这篇博客可能会将QGIS数据管理部分和ArcGIS数据管理进行对比学习. 1. 本地数据文件与数据库(矢量) 1.1 文件 QGIS用的是shp文件.kml文件.geojson文件较多 ...
- Tornado—接口调用时方法执行顺序
import tornado.web # web服务 import tornado.ioloop # I/O 时间循环 class MainHandler(tornado.web.RequestHan ...
- C# 让你解决方案乱七八糟的DLL放入指定文件夹
嗯,大家的解决方案可能会有许多dll,这样不美观,而且也麻烦. 很多小白都不知道如何将这些dll放到如自己程序的bin文件夹下. 本渣今天来试着将dll复制到指定的文件夹下~ 比如我之前做的一个Win ...
- 25.推荐---协同过滤(Collaborative Filtering)
协同过滤需要注意的三点: gray sheep(有人喜欢追求特别,协同过滤一般只能从共同的人或物间找相似) shilling attack(水军刷好评导致数据错误,无法带来精确的推荐) cold st ...