ViewPager是一个常用的android组件,不过通常我们使用ViewPager的时候不能实现左右无限循环滑动,在滑到边界的时候会看到一个不能翻页的动画,可能影响用户体验。此外,某些区域性的ViewPager(例如展示广告或者公告之类的ViewPager),可能需要自动轮播的效果,即用户在不用滑动的情况下就能够看到其他页面的信息。
一、PagerAdapter:
我们知道ViewPager自带的滑动效果非常出色,因此我们基本不需要处理这个滑动,只处理内容的显示。而内容的显示是由Adapter控制的,因此这里重点就是这个Adapter了。为简单起见,本例的每个View直接是一张图片。下面是Adapter的代码:
(
自定义PagerAdapter类 : 我们需要自定义一个类, 去继承PageAdapter, 至少实现下面四个方法 :
destroyItem(View container, int position, Object object) :
作用 :删除container中指定位置position的页面;
参数 : container 就是容器, 这里指的是ViewPager对象, position就是删除的页面索引;
int getCount() :
作用 :获取ViewPager页面的个数;
返回值 : ViewPager页面个数;
Object instantiateItem(View container, int position) :
作用 :在给定的位置创建页面, PageAdapter负责向指定的position位置添加View页面;
参数 : container容器就是ViewPager, position指的是ViewPager的索引;
返回值 : 返回代表新的一页的对象;
boolean isViewFromObject(View view, Object object) :
作用 :决定instantiateItem()方法返回的Object对象是不是需要显示的页面关联, 这个方法必须要有;
参数 : view 要关联的页面, object instantiateItem()方法返回的对象;
返回值 : 是否要关联显示页面与 instantiateItem()返回值;
)
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; } }
这里有几个地方需要注意:
1.
getCount() 方法的返回值:这个值直接关系到ViewPager的“边界”,因此当我们把它设置为Integer.MAX_VALUE之后,用户基本就看不到这个边界了(估计滑到这里的时候电池已经挂了吧o_O)。当然,通常情况下设置为100倍实际内容个数也是可以的,之前看的某个实现就是这么干的。
2.
instantiateItem() 方法position的处理:由于我们设置了count为 Integer.MAX_VALUE,因此这个position的取值范围很大很大,但我们实际要显示的内容肯定没这么多(往往只有几项),所以这里肯定会有求模操作。但是,简单的求模会出现问题:考虑用户向左滑的情形,则position可能会出现负值。所以我们需要对负值再处理一次,使其落在正确的区间内。
3.
instantiateItem() 方法父组件的处理:通常我们会直接addView,但这里如果直接这样写,则会抛出IllegalStateException。假设一共有三个view,则当用户滑到第四个的时候就会触发这个异常,原因是我们试图把一个有父组件的View添加到另一个组件。但是,如果直接写成下面这样:
(ViewGroup)view.getParent().removeView(view);
则又会因为一开始的时候组件并没有父组件而抛出NullPointerException。因此,需要进行一次判断。也就是上面的代码。
4.
destroyItem() 方法:由于我们在instantiateItem()方法中已经处理了remove的逻辑,因此这里并不需要处理。实际上,实验表明这里如果加上了remove的调用,则会出现ViewPager的内容为空的情况。
二、轮播效果的实现:使用Handler进行更新
这里我定义了一个Handler来处理ViewPager的轮播。所谓的“轮播”效果实现起来是这样的:每隔一定时间(这里是3秒)切换一次显示的页面。通过控制各页面以一定顺序循环播放,就达到了轮播的效果。为此,我们可以使用Handler的sendEmptyMessageDelayed()方法来实现定时更新,并注意用户也可能会对带有轮播效果的ViewPager手动进行滑动操作,因此我认为用户这时候是希望查看指定页面的,这时候应该取消轮播。下面是这个Handler的实现:
private static class ImageHandler extends Handler{ /** * 请求更新显示的View。 */ protected static final int MSG_UPDATE_IMAGE = 1; /** * 请求暂停轮播。 */ protected static final int MSG_KEEP_SILENT = 2; /** * 请求恢复轮播。 */ protected static final int MSG_BREAK_SILENT = 3; /** * 记录最新的页号,当用户手动滑动时需要记录新页号,否则会使轮播的页面出错。 * 例如当前如果在第一页,本来准备播放的是第二页,而这时候用户滑动到了末页, * 则应该播放的是第一页,如果继续按照原来的第二页播放,则逻辑上有问题。 */ protected static final int MSG_PAGE_CHANGED = 4; //轮播间隔时间 protected static final long MSG_DELAY = 3000; //使用弱引用避免Handler泄露.这里的泛型参数可以不是Activity,也可以是Fragment等 private WeakReference<MainActivity> weakReference; private int currentItem = 0; protected ImageHandler(WeakReference<MainActivity> wk){ weakReference = wk; } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d(LOG_TAG, "receive message " + msg.what); MainActivity activity = weakReference.get(); if (activity==null){ //Activity已经回收,无需再处理UI了 return ; } //检查消息队列并移除未发送的消息,这主要是避免在复杂环境下消息出现重复等问题。 if (activity.handler.hasMessages(MSG_UPDATE_IMAGE)){ activity.handler.removeMessages(MSG_UPDATE_IMAGE); } switch (msg.what) { case MSG_UPDATE_IMAGE: currentItem++; activity.viewPager.setCurrentItem(currentItem); //准备下次播放 activity.handler.sendEmptyMessageDelayed(MSG_UPDATE_IMAGE, MSG_DELAY); break; case MSG_KEEP_SILENT: //只要不发送消息就暂停了 break; case MSG_BREAK_SILENT: activity.handler.sendEmptyMessageDelayed(MSG_UPDATE_IMAGE, MSG_DELAY); break; case MSG_PAGE_CHANGED: //记录当前的页号,避免播放的时候页面显示不正确。 currentItem = msg.arg1; break; default: break; } } }
下面是MainActivity的代码,主要是加载View和对ViewPager进行初始化设置。因为代码量比较少,重要的部分已经加了注释,就不赘述了。
public class MainActivity extends Activity { private static final String LOG_TAG = "MainActivity"; private ImageHandler handler = new ImageHandler(new WeakReference<MainActivity>(this)); private ViewPager viewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化iewPager的内容 viewPager = (ViewPager) findViewById(R.id.main_viewpager); LayoutInflater inflater = LayoutInflater.from(this); ImageView view1 = (ImageView) inflater.inflate(R.layout.item, null); ImageView view2 = (ImageView) inflater.inflate(R.layout.item, null); ImageView view3 = (ImageView) inflater.inflate(R.layout.item, null); view1.setImageResource(R.drawable.ics); view2.setImageResource(R.drawable.jellybean); view3.setImageResource(R.drawable.kitkat); ArrayList<ImageView> views = new ArrayList<ImageView>(); views.add(view1); views.add(view2); views.add(view3); viewPager.setAdapter(new ImageAdapter(views)); viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { //配合Adapter的currentItem字段进行设置。 @Override public void onPageSelected(int arg0) { handler.sendMessage(Message.obtain(handler, ImageHandler.MSG_PAGE_CHANGED, arg0, 0)); } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } //覆写该方法实现轮播效果的暂停和恢复 @Override public void onPageScrollStateChanged(int arg0) { switch (arg0) { case ViewPager.SCROLL_STATE_DRAGGING: handler.sendEmptyMessage(ImageHandler.MSG_KEEP_SILENT); break; case ViewPager.SCROLL_STATE_IDLE: handler.sendEmptyMessageDelayed(ImageHandler.MSG_UPDATE_IMAGE, ImageHandler.MSG_DELAY); break; default: break; } } }); viewPager.setCurrentItem(Integer.MAX_VALUE/2);//默认在中间,使用户看不到边界 //开始轮播效果 handler.sendEmptyMessageDelayed(ImageHandler.MSG_UPDATE_IMAGE, ImageHandler.MSG_DELAY); }//end of onCreate }//end of MainActivity
四、小圆点导航策略:(代码放入Activity中)
圆点存放策略 : 所有的小圆点都放在一个ViewGroup中, 有两种圆点, 一种是当前显示的, 一种是没激活的, 这里我们将一组圆点分别放入ImageView中, 并且将这些ImageView组装起来放到ViewGroup中即可;
圆点导航初始化 : 最初默认显示第一个页面, 第一个圆点激活, 根据ViewPager个数初始化圆点的个数, 组装圆点的时候, 第一个圆点状态激活代码如下 :
private void initCirclePoint(){ ViewGroup group = (ViewGroup) findViewById(R.id.viewGroup); imageViews = new ImageView[pageViews.size()]; //广告栏的小圆点图标 for (int i = 0; i < pageViews.size(); i++) { //创建一个ImageView, 并设置宽高. 将该对象放入到数组中 imageView = new ImageView(this); imageView.setLayoutParams(new LayoutParams(20,20)); imageViews[i] = imageView; //初始值, 默认第0个选中 if (i == 0) { imageViews[i] .setBackgroundResource(R.drawable.point_focused); } else { imageViews[i] .setBackgroundResource(R.drawable.point_unfocused); } //将小圆点放入到布局中 group.addView(imageViews[i]); } }
ViewPager页面改变时圆点导航随之改变 : 获取ViewPager当前显示页面索引,重新组装ViewGroup中的圆点排列顺序, 这个方法在ViewPager页面改变监听器中实现:
// ViewPager 页面改变监听器 private final class AdPageChangeListener implements OnPageChangeListener { //页面滚动状态发生改变的时候触发 @Override public void onPageScrollStateChanged(int arg0) { } //页面滚动的时候触发 @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } //页面选中的时候触发 @Override public void onPageSelected(int arg0) { //获取当前显示的页面是哪个页面 atomicInteger.getAndSet(arg0); //重新设置原点布局集合 for (int i = 0; i < imageViews.length; i++) { imageViews[arg0] .setBackgroundResource(R.drawable.point_focused); if (arg0 != i) { imageViews[i] .setBackgroundResource(R.drawable.point_unfocused); } } } }
五、XML:
主布局文件activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> <LinearLayout android:id="@+id/ll" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="24.0dp" android:orientation="horizontal" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:clickable="true" android:padding="15.0dip" android:src="@drawable/dot" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:clickable="true" android:padding="15.0dip" android:src="@drawable/dot" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:clickable="true" android:padding="15.0dip" android:src="@drawable/dot" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:clickable="true" android:padding="15.0dip" android:src="@drawable/dot" /> </LinearLayout> </RelativeLayout> splash.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/splash" android:orientation="vertical" > </LinearLayout> view1.xml view2.xml view3.xml : <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/guide1" /> </RelativeLayout> view4.xml : <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:adjustViewBounds="false" android:background="@drawable/guide4" android:focusable="true" android:scaleType="centerCrop" /> <ImageView android:id="@+id/iv_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="108dp" android:background="@drawable/button" android:focusable="true" /> </RelativeLayout> drawable文件夹下新建dot.xml: <?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/page_indicator_unfocused" android:state_enabled="true"/> <item android:drawable="@drawable/page_indicator_focused" android:state_enabled="false"/> </selector>
http://img.blog.csdn.net/20130522225518853
最后发一个ViewPager的使用文章:
Android ViewPager使用详解 - 极致的专注 无限的热情 - 博客频道 - CSDN.NET
http://blog.csdn.net/wangjinyu501/article/details/8169924