ViewPager是一个常用的android组件,不过通常我们使用ViewPager的时候不能实现左右无限循环滑动,在滑到边界的时候会看到一个不能翻页的动画,可能影响用户体验。此外,某些区域性的ViewPager(例如展示广告或者公告之类的ViewPager),可能需要自动轮播的效果,即用户在不用滑动的情况下就能够看到其他页面的信息。
循环滑动效果的实现:PagerAdapter
我们知道ViewPager自带的滑动效果非常出色,因此我们基本不需要处理这个滑动,只处理内容的显示。而内容的显示是由Adapter控制的,因此这里重点就是这个Adapter了。为简单起见,本例的每个View直接是一张图片。下面是Adapter的代码:
private class ImageAdapter extends PagerAdapter{
private ArrayList<ImageView> viewlist;
public ImageAdapter(ArrayList<ImageView> viewlist) {
this.viewlist = viewlist;
}
@Override
public int getCount() {
//设置成最大,使用户看不到边界
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==arg1;
}
@Override
public void destroyItem(ViewGroup container, int position,
Object object) {
//Warning:不要在这里调用removeView
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
//对ViewPager页号求模取出View列表中要显示的项
position %= viewlist.size();
if (position<0){
position = viewlist.size()+position;
}
ImageView view = viewlist.get(position);
//如果View已经在之前添加到了一个父组件,则必须先remove,否则会抛出IllegalStateException。
ViewParent vp =view.getParent();
if (vp!=null){
ViewGroup parent = (ViewGroup)vp;
parent.removeView(view);
}
container.addView(view);
//add listeners here if necessary
return view;
}
}
这里有几个地方需要注意:
getCount() 方法的返回值:这个值直接关系到ViewPager的“边界”,因此当我们把它设置为Integer.MAX_VALUE之后,用户基本就看不到这个边界了(估计滑到这里的时候电池已经挂了吧o_O)。当然,通常情况下设置为100倍实际内容个数也是可以的,之前看的某个实现就是这么干的。
instantiateItem() 方法position的处理:由于我们设置了count为 Integer.MAX_VALUE,因此这个position的取值范围很大很大,但我们实际要显示的内容肯定没这么多(往往只有几项),所以这里肯定会有求模操作。但是,简单的求模会出现问题:考虑用户向左滑的情形,则position可能会出现负值。所以我们需要对负值再处理一次,使其落在正确的区间内。
destroyItem() 方法:由于我们在instantiateItem()方法中已经处理了remove的逻辑,因此这里并不需要处理。实际上,实验表明这里如果加上了remove的调用,则会出现ViewPager的内容为空的情况。
轮播效果的实现:使用Handler进行更新
这里我定义了一个Handler来处理ViewPager的轮播。所谓的“轮播”效果实现起来是这样的:每隔一定时间(这里是3秒)切换一次显示的页面。通过控制各页面以一定顺序循环播放,就达到了轮播的效果。为此,我们可以使用Handler的sendEmptyMessageDelayed()方法来实现定时更新,并
注意用户也可能会对带有轮播效果的ViewPager手动进行滑动操作,因此我认为用户这时候是希望查看指定页面的,这时候应该取消轮播。下面是这个Handler的实现:
案例1
一、ViewPager填充图片
1.1 布局中申明
由于是显示广告条,所以高度要固定住
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="120dp"/>
1.2 代码中设置页面数据
准备显示图片控件的集合
// 准备显示的图片集合
mList = new ArrayList<>();
for (int i = 0; i < mImages.length; i++) {
ImageView imageView = new ImageView(this);
// 将图片设置到ImageView控件上
imageView.setImageResource(mImages[i]);
// 将ImageView控件添加到集合
mList.add(imageView);
}
自定义类书写适配器
@Override
public Object instantiateItem(ViewGroup container, int position) {
// return super.instantiateItem(container, position);
// 将图片控件添加到容器
container.addView(mList.get(position));
// 返回
return mList.get(position);
}
二、底部小圆点显示逻辑
原理分析:底部的小圆点时浮动在ViewPager上面的的,所以应该是一个RelativeLayout布局。
ViewPager页面切换时小圆点的颜色不一样,所以需要对小圆点做选择器,并且对ViewPager进行监听。
2.1 布局申明
需要用一个RelativeLayout将ViewPager和包裹圆点的LinearLayout包裹起来
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="120dp">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="120dp"/>
<LinearLayout
android:id="@+id/pointgroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="3dp"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
2.2 制作小圆点颜色选择器
选择器的选中状态应该设置为selected,因为对ViewPager监听时可以设置selected的属性
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/shape_point_normal" android:state_selected="false"/>
<item android:drawable="@drawable/shape_point_selected" android:state_selected="true"/>
</selector>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#66000000"/>
</shape
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
2.3 将小圆点添加到LinearLayout容器
小圆点其实就是一个ImageView,所以在做出ViewPager的页面图片时,一起把小圆点也做了
初始化ImageView添加到LinearLayout之前,需要设置小圆点的布局参数,包括位置和大小
LinearLayout pointGroup = (LinearLayout) findViewById(R.id.pointgroup);
for (int i = 0; i < mImages.length; i++) {
// 制作底部小圆点
ImageView pointImage = new ImageView(this);
pointImage.setImageResource(R.drawable.shape_point_selector);
// 设置小圆点的布局参数
int PointSize = getResources().getDimensionPixelSize(R.dimen.point_size);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(PointSize, PointSize);
if (i > 0) {
params.leftMargin = getResources().getDimensionPixelSize(R.dimen.point_margin);
pointImage.setSelected(false);
} else {
pointImage.setSelected(true);
}
pointImage.setLayoutParams(params);
// 添加到容器里
pointGroup.addView(pointImage);
}
三、小圆点随着ViewPager切换移动
其实就是对ViewPager设置滑动监听,当滑动到每一页时就设置小圆点为选中状态,这样小圆点就显示白色,其他页面就设置为未选中状态显示灰色。
// 对ViewPager设置滑动监听
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
int lastPosition;
@Override
public void onPageSelected(int position) {
// 页面被选中
// 设置当前页面选中
pointGroup.getChildAt(position).setSelected(true);
// 设置前一页不选中
pointGroup.getChildAt(lastPosition).setSelected(false);
// 替换位置
lastPosition = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
经过前面的三步设置后就能显示一个简单的广告条了,这里再对其添加一个滑动到最后一页后再滑还能滑动到首页的功能。
四、无限滑动的ViewPager
实现原理: ViewPager之所以滑动到左右能显示页面,其实是因为左右都存在即将要显示的页面。当左右有很多页面时我们就能一直滑动,没有时就不能滑动。所以原理就是让ViewPager的左右都有很多的页面。
4.1 修改getCount方法
ViewPager能显示多少个页面全由getCount方法说了算,所以我们首先要改造它。
@Override
public int getCount() {
// 返回整数的最大值
return Integer.MAX_VALUE;
}
4.2 修改instantiateItem方法
因为position变了,所以显示的位置也变了,这里需要进行取%运算,来还原position。
// 修改position
position = position % mList.size();
4.3 修改ViewPager监听器里的onPageSelected
用到了position就要修改。
position = position % mList.size();
修改之后,ViewPager当前页的右边就有了无数的页面,但是因为%了mList.size(),就只会显示mList.size()的大小,这样就实现了无限滑动轮播
五、无限自动轮播的广告图
实现原理:在前面四步的基础上,在代码里添加一个Handler,不断的给自己发消息就好了。
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
int currentPosition = viewPager.getCurrentItem();
if(currentPosition == viewPager.getAdapter().getCount() - 1){
// 最后一页
viewPager.setCurrentItem(0);
}else{
viewPager.setCurrentItem(currentPosition + 1);
}
// 一直给自己发消息
mHandler.postDelayed(this,5000);
}
},5000);
案例2
public class RoolViewPager extends ViewPager {
private int downX;
private int downY;
public RoolViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public RoolViewPager(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//事件分发的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//请求父控件不要拦截事件,true:不拦截,false:拦截
//getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
//1.需要判断是左右滑动还是上下滑动,因为只有左右才是viewpager手动滑动的操作
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//getParent().requestDisallowInterceptTouchEvent(false);
//获取按下的x和y的坐标
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//获取移动的x和y的坐标
int moveX = (int) ev.getX();
int moveY = (int) ev.getY();
//判断是上下还是左右滑动
if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) {
//左右
//从右往左,如果是最后一个条目,父控件拦截事件,实现切换界面的操作,如果不是最后一个条目,切换下一张图片
//getAdapter() : 获取ViewPager设置的adapter
if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) {
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){
getParent().requestDisallowInterceptTouchEvent(true);
}
//从左往右,如果是第一个条目,父控件拦截事件,打开侧拉菜单,如果不是第一个条目,切换到上一张图片
else if(downX - moveX < 0 && getCurrentItem() == 0){
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX < 0 && getCurrentItem() > 0){
getParent().requestDisallowInterceptTouchEvent(true);
}
}else{
//上下
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="185dp" >
<!-- xmlns:app="http://schemas.android.com/apk/res/com.itheima.zhbj97" -->
<com.itheima.zhbj97.ui.RoolViewPager
android:id="@+id/menunewscenteritem_vp_viewpager"
android:layout_width="match_parent"
android:layout_height="185dp"
></com.itheima.zhbj97.ui.RoolViewPager>
<!-- layout_alignBottom : 在哪个控件的底部 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="#aa000000"
android:layout_alignBottom="@+id/menunewscenteritem_vp_viewpager"
>
<TextView
android:id="@+id/menunewscenteritem_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="蜗居生活"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
/>
<!-- radius : 点的半径
fillColor :点的填充颜色
pageColor : 界面的点的颜色
strokeColor : 边框的颜色
strokeWidth : 边框的宽度
-->
<com.viewpagerindicator.CirclePageIndicator
android:id="@+id/menunewscenteritem_cpi_indicator"
android:padding="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:radius="3dp"
app:fillColor="#FF0000"
app:pageColor="#FFFFFF"
app:strokeWidth="0dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
/>
</RelativeLayout>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ptr="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- scrollbars : 设置listview的滚动条 -->
<com.handmark.pulltorefresh.library.PullToRefreshListView
android:id="@+id/menunewscenteritem_lv_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/darker_gray"
android:dividerHeight="1dp"
android:listSelector="@android:color/transparent"
android:scrollbars="none"
ptr:ptrMode="both"
></com.handmark.pulltorefresh.library.PullToRefreshListView>
</LinearLayout>
mViewPagerView = View.inflate(activity, R.layout.menunewscenter_viewpager, null);
mListViewPagerView = View.inflate(activity, R.layout.menunewscenteritem_listview, null);
//初始化控件
mViewPager = (RoolViewPager) mViewPagerView.findViewById(R.id.menunewscenteritem_vp_viewpager);
mTitle = (TextView) mViewPagerView.findViewById(R.id.menunewscenteritem_tv_title);
mIndicator = (CirclePageIndicator) mViewPagerView.findViewById(R.id.menunewscenteritem_cpi_indicator);
mPullListView = (PullToRefreshListView) mListViewPagerView.findViewById(R.id.menunewscenteritem_lv_listview);
1.设置VIewPager的adapter
private void showMsg(NewBean newBean) {
//1.ViewPager的数据
if (newBean.data.topnews.size() > 0) {
....
//1.2.通过viewpager展示数据
if (myadapter == null) {
myadapter = new Myadapter();
mViewPager.setAdapter(myadapter);
}else{
myadapter.notifyDataSetChanged();
}
}
//2.ListView的数据
}
2.adapter的操作
@Override
public Object instantiateItem(ViewGroup container, int position) {
View rootView = View.inflate(activity, R.layout.menunewsceteritem_viewpager_item, null);
ImageView mIcon = (ImageView) rootView.findViewById(R.id.item_iv_icon);
//因为图片是在服务器中,不在本地,所以需要从服务器获取图片,展示在imageView
//通过图片的路径请求服务器中的图片,存放到相应的imageView中
Glide.with(activity.getApplicationContext()).load(imagerUrls.get(position)).into(mIcon);
//将图片所在的布局添加到ViewPager中展示
container.addView(rootView);
return rootView;
}
ViewPager的界面点和文本的初始化操作
private void showMsg(NewBean newBean) {
//1.ViewPager的数据
if (newBean.data.topnews.size() > 0) {
...
//1.3.将viewpager和点的indicator关联
mIndicator.setViewPager(mViewPager);
mIndicator.setSnap(true);//快照,使用快照的方式显示点
//1.4.设置默认显示第一张图片,第一个文本,第一个点
mTitle.setText(titles.get(0));
mIndicator.onPageSelected(0);
mViewPager.setCurrentItem(0);//设置viewpager当前显示的界面,item:条目的索引
}
//2.ListView的数据
}
填充listview的数据将ViewPager的布局作为listView的头条目展示
1.将ViewPager的布局作为listview的头条目展示
private void showMsg(NewBean newBean) {
//1.ViewPager的数据
if (newBean.data.topnews.size() > 0) {
....
//1.5.将ViewPager所在的布局,添加到listView中
//获取listview的头条目的个数
if (mListView.getHeaderViewsCount()<1) {
mListView.addHeaderView(mViewPagerView);//给listview添加头条目
}
}
//2.ListView的数据
}
2.填充listview数据
private void showMsg(NewBean newBean) {
.....
//2.ListView的数据
if (newBean.data.news.size() > 0) {
mNews = newBean.data.news;
//设置listview的adapter展示数据
if (listViewAdapter == null) {
listViewAdapter = new MyListViewAdapter();
mListView.setAdapter(listViewAdapter);
}else{
listViewAdapter.notifyDataSetChanged();
}
}
}
ViewPager自动滑动操作
核心理念:每个一段时间,viewpager切换到下一个界面
1.通过handler设置viewpager的自动滑动操作
//因为showMsg方法实在processjson方法中调用的,而processJson是在缓存和获取最新数据的时候都会调用,最终会造成发送两个延迟消息,但是只需要一个延迟消息就可以了
if (handler == null) {
handler = new Handler(){
public void handleMessage(android.os.Message msg) {
//viewpager切换下一个界面的操作
//首先需要知道当前显示的界面
int currentItem = mViewPager.getCurrentItem();//获取当前显示界面的索引
//然后计算下一个界面的索引
//判断是否切换到最后一个界面,如果是最后一个界面了,切换回第一个界面
if (currentItem == imagerUrls.size()-1) {
currentItem=0;
}else{
currentItem++;
}
//设置viewpager显示下一个界面
mViewPager.setCurrentItem(currentItem);
//切换一次完成,还要紧接着切换第二次
handler.sendEmptyMessageDelayed(0, 3000);
};
};
handler.sendEmptyMessageDelayed(0, 3000);//只有执行此方法,才会发送延迟消息,不执行就不发送
}
2.设置viewpager的界面切换监听,实现切换界面显示界面对应的文本
//监听viewpager的界面切换,实现切换一个界面显示一个界面对应的文本
mViewPager.addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mTitle.setText(titles.get(position));
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// TODO Auto-generated method stub
}
@Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
}
});
viewpager的手动滑动
当滑动到小viewpager的最后一个界面的时候,外面的viewpager要将小的viewpager的触摸事件拦截,当滑动小的viewpager的不是最后一个界面的时候,外面的viewpager不拦截小viewpager的触摸事件
让小viewpager进行滑动操作
创建自定义Viewpager进行操作
//事件分发的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//请求父控件不要拦截事件,true:不拦截,false:拦截
//getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
//1.需要判断是左右滑动还是上下滑动,因为只有左右才是viewpager手动滑动的操作
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//getParent().requestDisallowInterceptTouchEvent(false);
//获取按下的x和y的坐标
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//获取移动的x和y的坐标
int moveX = (int) ev.getX();
int moveY = (int) ev.getY();
//判断是上下还是左右滑动
if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) {
//左右
//从右往左,如果是最后一个条目,父控件拦截事件,实现切换界面的操作,如果不是最后一个条目,切换下一张图片
//getAdapter() : 获取ViewPager设置的adapter
if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) {
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){
getParent().requestDisallowInterceptTouchEvent(true);
}
//从左往右,如果是第一个条目,父控件拦截事件,打开侧拉菜单,如果不是第一个条目,切换到上一张图片
else if(downX - moveX < 0 && getCurrentItem() == 0){
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX < 0 && getCurrentItem() > 0){
getParent().requestDisallowInterceptTouchEvent(true);
}
}else{
//上下
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
ViewPager和View的事件响应规则
如果是缓慢的移动很短的距离,viewpager和view的事件都会执行
如果是快速滑动很长的距离,view的事件会执行cancel事件,结束view的触摸操作,只去viewpager的事件
具体操作
//设置view的触摸事件事件,实现按下viewpager停止自动滑动,抬起,viewpager重新进行自动滑动操作
rootView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下viewpager停止滑动
handler.removeCallbacksAndMessages(null);//取消handler发送延迟消息,如果是null,全部handler都会被取消发送消息
break;
case MotionEvent.ACTION_UP:
//抬起viewpager重新滑动
handler.sendEmptyMessageDelayed(0, 3000);
break;
case MotionEvent.ACTION_CANCEL:
//view的事件取消执行的操作
handler.sendEmptyMessageDelayed(0, 3000);
break;
}
//如果想要事件执行,返回true,返回事件不执行
return true;
}
});
自定义RoolViewPager
public class RoolViewPager extends ViewPager {
private int downX;
private int downY;
public RoolViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public RoolViewPager(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//事件分发的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//请求父控件不要拦截事件,true:不拦截,false:拦截
//getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
//1.需要判断是左右滑动还是上下滑动,因为只有左右才是viewpager手动滑动的操作
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//getParent().requestDisallowInterceptTouchEvent(false);
//获取按下的x和y的坐标
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//获取移动的x和y的坐标
int moveX = (int) ev.getX();
int moveY = (int) ev.getY();
//判断是上下还是左右滑动
if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) {
//左右
//从右往左,如果是最后一个条目,父控件拦截事件,实现切换界面的操作,如果不是最后一个条目,切换下一张图片
//getAdapter() : 获取ViewPager设置的adapter
if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) {
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){
getParent().requestDisallowInterceptTouchEvent(true);
}
//从左往右,如果是第一个条目,父控件拦截事件,打开侧拉菜单,如果不是第一个条目,切换到上一张图片
else if(downX - moveX < 0 && getCurrentItem() == 0){
getParent().requestDisallowInterceptTouchEvent(false);
}else if(downX - moveX < 0 && getCurrentItem() > 0){
getParent().requestDisallowInterceptTouchEvent(true);
}
}else{
//上下
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
}
xml使用
布局文件中使用
<com.itheima.zhbj97.ui.RoolViewPager
android:id="@+id/menunewscenteritem_vp_viewpager"
android:layout_width="match_parent"
android:layout_height="185dp"
></com.itheima.zhbj97.ui.RoolViewPager>