ViewPager(一) 初相识
ViewPager(二) Adapter的爱恨情仇
ViewPager(三) 两个熊孩子天生不一样
前边我们了解了ViewPager适配器的使用,并且从源码角度分析了造成这样区别的原因,其实把适配器放在ViewPager系列的开头来讲,是为了让用户对使用ViewPager有个宏观的把控,这样的话在我们涉及具体用法的时候,都是在此基础上进行拓展,这也是区别于其他博客的内容安排。
本来这篇是 ViewPager使用技巧打算一篇写完。。。
可是发现10000多字,这样读者看完会很难受,所以我打算分四篇
1、换页监听与换页方法
2、懒加载及预加载定制
3、设置间距与添加转场动画
4、轮播、禁止滑动与指示器的配合
当我们在viewpager滑动的时候需要给页面设置页码或者标题,页码是为了提醒用户浏览的总量以及当前所处的位置,而标题是为了给用户文字信息提示,并且也可以在广告场景作为图片的补充。
实现页面监听,调用的是ViewPager的下列方法
addOnAdapterChangeListener(OnAdapterChangeListener listener)
通过页面改变监听器进行监听。
OnPageChangeListener是ViewPager的内部类,是一个接口。
有三个必须实现的方法:
1、void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
这个方法是在滚动的时候进行回调,分两种情况,一种是代码控制的滚动,还是用户手划滚动,都能监听的到,这个方法可以做一些滚动时的过程控制,注意:在初始化viewpager和Adapter后会回调这个方法一次。
2、void onPageSelected(int position);
这个方法是在新页面已经呈现,并且动作还没有执行完全的时候回调的,这个方法就是我们用来动态设置页码的方法,只需要对position+1,就能对应当前页码,设置页面的标题也是在这个时候做。注意:这个方法在第一次加载viewpager后不会回调,所以无论是设置页码还是设置标题都需要在viewpager初始化之后手动设置一次,也就是手动设置首先呈现的页面的相关信息。
3、void onPageScrollStateChanged(int state);
这个方法是对滑动方式状态的监听,可以根据state值来做判断,其中ViewPager#SCROLL_STATE_IDLE为切换页面动画结束,或者处于idle闲置状态
ViewPager#SCROLL_STATE_DRAGGING为用户正在滑动时的状态回调,此时滑动未结束
ViewPager#SCROLL_STATE_SETTLING为非用户滑动,代码设置切换的状态,注意:这个状态会在手势滑动翻页(onPageSelected回调之前)回调一次这个状态值
这个方法可以做一些关于不同滑动方式的滑动方案的区别设计,用户滑动的时候执行一套逻辑,代码切换的时候执行一套逻辑,并且还可以判断切换动画是否已经结束,是否处于idle状态
如果我们只是改变当前页码并设置标题并不需要上面的三个方法都监听,这时候就要用到谷歌为这个Listener做的缺省适配SimpleOnPageChangeListener,这是一个ViewPager的静态内部类,实现了上述接口的三个方法,所以我们就可以在回调的时候写出我们需要的回调方法,而不会出现编译报错。
我们来对比下(上边的代码是非缺省,下边是缺省代码):
private int mCurrentPos = 0;
mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
//下面三个回调缺一不可,否则就会编译报错
@Override
public void onPageScrolled(int position, float positionOffset, int
positionOffsetPixels) {
Log.e("PageChange-Scroll", "position:" + position + ",positionOffset:" +
positionOffset + ",offsetPixels:" + positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
Log.e("PageChange-Select", "position:" + position);
//给标题textview设置对应标题,设置时机在这里
mTitleTv.setText(mTags[position]);
mCurrentPos = position;
}
@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
case ViewPager.SCROLL_STATE_IDLE:
Log.e("PageChange-State", "state:SCROLL_STATE_IDLE(滑动闲置或滑动结束)");
break;
case ViewPager.SCROLL_STATE_DRAGGING:
Log.e("PageChange-State", "state:SCROLL_STATE_DRAGGING(手势滑动中)");
break;
case ViewPager.SCROLL_STATE_SETTLING:
Log.e("PageChange-State", "state:SCROLL_STATE_SETTLING(代码执行滑动中)");
break;
default:
break;
}
}
});
private int mCurrentPos = 0;
mPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
//只实现自己想要的回调
@Override
public void onPageSelected(int position) {
Log.e("SimplePageChange-Select", "position:" + position);
//给标题textview设置对应标题,设置时机在这里
mTitleTv.setText(mTags[position]);
mCurrentPos = position;
}
});
对于页面切换的监听注册还有个过期的方法
setOnPageChangeListener(OnAdapterChangeListener listener)
这个方法尽量不要用,是独占式的,不能注册多个,如果我们在不同的界面都想要同时监听,就没办法了
addOnPageChangeListener实际上是维护一个list,在调用这个方法,就是网list里加元素,同时还有移除方法removeOnPageChangeListener,如果注册了多个还可以调用clearOnPageChangeListeners()就可以一次清除所有注册的监听。而setOnPageChangeListener是维护了一个单独的变量。
每次观察者模式通知事件的时候,先回调单独的,然后遍历list里的元素,分别回调。
ViewPager允许用户代码控制翻页,并且可以设置翻页的状态,有个重载方法:
setCurrentItem(int item) //设置对应位置显示
setCurrentItem(int item, boolean smoothScroll) //设置对应的位置显示,并且是否开启缓慢滑动
在我们设置跳转页面的时候(比如界面上增加一个上一页按钮和下一页按钮)可以调用这两个方法来设置代码跳转,第一个方法,不传boolean值,默认的值为判断是否第一次布局的状态,并取反。所以除了一进来是false,之后的都是true(取反后的)。
调用这个方法,我们需要在全局存一个当前位置int类型的position变量(如果不想保存,还可以调用ViewPager的getCurrentItem()
方法获取当前位置),在监听按钮的时候需要对当前的全局位置变量+1或者-1,虽然在ViewPager的内部是有做越界判断的,但是如果你想提醒用户是否到了第一页和最后一页,你需要自己代码做判断。
而通过下面的方法你只需要根据返回的布尔值做相应的提示。
boolean arrowScroll(int direction) //控制翻页
其中direction用的是View的几个静态常量:
表示上一页:View.FOCUS_LEFT , View.FOCUS_BACKWARD(二选一)
表示下一页:View.FOCUS_RIGHT, View.FOCUS_FORWARD(二选一)
调用上述方法传相应的值,然后就能翻页了,根据返回值判断,是否到了第一页或者最后一页(如果是向前翻,返回false则是到了第一页;如果是向后翻,则是到了最后一页)。
上边两种方法都能实现相邻翻页,但是前边方法setCurrentItem
可以跳转任何一页,进入页面不想显示第一页可以通过这个方法设置(注意数组越界),并且当用户想要去哪一页也可指定跳转。而后边的方法arrowScroll
只能相邻跳转,但是好处就是根据返回的布尔值判断是否到头了,不用自己去做判断和控制。
首先我们上方法-----------
boolean beginFakeDrag() //开始假滑
Void fakeDragBy(float xOffset) //控制假滑移动水平方向距离
void endFakeDrag() //结束假滑
boolean isFakeDragging() //返回当前是否假滑状态中
以上三个方法用来完成一套完整的假滑流程操作。
执行具体的动作方法是第二个,fakeDragBy(float xOffset)
,并将滑动距离的参数传入后,viewPager就会执行模拟滑动操作了。
但是,没这么简单,因为,你随意调用这个方法,会抛出一个IllegalStateException异常,具体是什么异常呢,我们能从message中找到答案:
No fake drag in progress. Call beginFakeDrag first.
这个时候就该我们第一个方法出场了。所以以上的四个方法的顺序是
1、在假滑之前判断一下当前是否正处于假滑的过程中,是否还没有结束isFakeDragging()
返回的boolean值就会告诉我们,现在是否已经结束,能不能安全开始启动一个新的假滑,在返回false后再执行beginFakeDrag()
启动假滑(重置参数,保证一次只能有一个假滑动作存在);
2、启动假滑后调用fakeDragBy(float xOffset)
并传入你想要滑动的距离。然后viewpager就会执行;
3、在我们最后不需要假滑,或者停止假滑的时候一定不要忘记调用endFakeDrag()
释放当前假滑流程,并且这个时候我们仍然需要通过isFakeDragging()
来判断当前状态,如果返回true,你就能安全的停止了,如果是false的话,就不用调了,因为没有执行任何动作当然是不用停止的。如果不做判断的调,还会抛出和上边一样的异常来。而这个时候的判断正好与准备启动那个假滑开始判断相反。
举个栗子!!!(我们用了SeekBar这个原生控件)
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.i("SEEK_BAR_FAKE", "Track changed progress:" + progress);
//获取进度,经简单计算后设置假滑位移,注意这里达到临界值就会翻页的
if (mPager.isFakeDragging()) {
mPager.fakeDragBy(mSource - progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.i("SEEK_BAR_FAKE", "Start Track");
//seekbar滑动开始的时候打开假滑,这个方法也是首先调用的
if (!mPager.isFakeDragging()) {
mPager.beginFakeDrag();
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.i("SEEK_BAR_FAKE", "End Track");
//seekbar滑动结束的时候,最后调用,用来关闭假滑
if (mPager.isFakeDragging())
mPager.endFakeDrag();
mSeekBar.setProgress(mSource);
}
});
我们再来说下这一套方法究竟用来干什么?
在某些场景下,用户可能不想要直接滑动Viewpager,但是在操作其他控件的时候也能让Viewpager执行滑动操作,好像有人在滑动它一样。这下明白了,这个是google为Viewpager和其他控件实现联动机制方法。
再简单说下源码:
在beginFakeDrag的时候里边有一句很重要的代码
MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
mVelocityTracker.addMovement(ev);
在fakeDragBy(float xOffset)里边有这么几句代码,其中mLastMotionX是根据你传入的移动偏量重新计算的移动变量
MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
mLastMotionX, 0, 0);
mVelocityTracker.addMovement(ev);
而在endFakeDrag()方法中只是在判断当前滑动是否到了可以翻页的边界值,停止滑动后具体显示哪一页,其次就是讲一些过程控制变量重置。
这是进阶的第一部分,我们接着看下一部分懒加载及预加载定制