andorid|Android ViewPager的使用总结

ViewPager的基本使用 ViewPager继承自ViewGroup,是一个View容器。用于装载多个View页面,可以在一个固定空间切换多个页面显示。
ViewPager使用也很简单,主要代码是实现PagerAdapter适配器。首先适配器继承PagerAdapter,一般需要实现如下几个方法:

  • getCount()
  • isViewFromObject(View, Object)
  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object)
代码示例如下:
private class ViewPagerAdapter extends PagerAdapter {// 获取要滑动的控件的数量,在这里我们以滑动的广告栏为例,那么这里就应该是展示的广告图片的ImageView数量 @Override public int getCount() { return images.size(); }// 来判断显示的是否是同一张图片,这里我们将两个参数相比较返回即可。这里判断当前的View是否和对应的key关联。这里的key和instantiateItem方法的返回值一致。 @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; }// PagerAdapter只缓存三张要显示的图片,如果此时滑动到第三页时,第一页就会调用该方法去销毁相应的View。 @Override public void destroyItem(ViewGroup view, int position, Object object) { view.removeView(images.get(position)); }// 该方法是按需调用,默认情况先调用三次,将三个即将使用View页面添加到ViewGroup中,当你滑动到第二页View时,ViewPager会调用一次该方法将第四个View页面添加到ViewGroup中。该方法返回值作为key和对应位置的View关联起来。用于isViewFromObject方法判断当前View和key是否关联的。 @Override public Object instantiateItem(ViewGroup view, int position) { view.addView(images.get(position)); return images.get(position); } }

一般情况下,我们只需实现PagerAdapter适配器中的以上四个方法即可实现简单的ViewPager图片轮播效果。
自定义ViewPagerIndicator andorid|Android ViewPager的使用总结
文章图片

现在很多app的启动引导页都有一个图片轮播来展示该app的一些功能,几乎每个引导页面的下方都有相应的指示点来指示当前用户切换到哪个页面。由于ViewPager自己不支持设置指示点,所以我们只能自定义一个。现在我们来实现一个类似app启动带有指示点的引导页。
实现思路:自定义一个ViewPagerIndicator类,继承自FrameLayout,先往ViewPagerIndicator中添加一个ViewPager,在往其中添加一个指示点ViewIndicator布局。让ViewIndicator布局在整个ViewPager之上。所以这里需要自定义两个View:
  1. ViewIndicator
  2. ViewPagerIndicator
代码实现如下:
自定义ViewIndicator
public class ViewIndicator extends LinearLayout { private int mMarginLeft = 20; //px private int mNum; private List imageViews = new ArrayList<>(); public ViewIndicator(Context context) { this(context, null); }public ViewIndicator(Context context, AttributeSet attrs) { this(context, attrs, 0); }public ViewIndicator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }private void init() { setOrientation(HORIZONTAL); setGravity(Gravity.CENTER); }/** * 设置指示点的间距 * * @param marginLeft */ public void setMarginLeft(int marginLeft) { mMarginLeft = marginLeft; }/** * 设置indicator指示点的个数以及初始化 * * @param num */ public void setIndicatorViewNum(int num) { removeAllViews(); imageViews.clear(); mNum = num; for (int i = 0; i < num; i++) { ImageView imageView = new ImageView(this.getContext()); LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); if (i == 0) { imageView.setImageResource(R.drawable.circle_bg); } else { params.leftMargin = mMarginLeft; imageView.setImageResource(R.drawable.circle_bg1); } imageViews.add(imageView); addView(imageView, params); } }/** * 设置当前index的位置 * * @param index */ public void setCurrentIndex(int index) { for (int i = 0; i < mNum; i++) { if (i == index) { imageViews.get(i).setImageResource(R.drawable.circle_bg); } else { imageViews.get(i).setImageResource(R.drawable.circle_bg1); } } }

自定义CustomViewPager
public class CustomViewPager extends ViewPager { private static final String TAG = "CustomViewPager"; private MyPagerAdapter mPagerAdapter; private int mTotal; //总数 private int mCurrentPos; //当前位置 public static final int AUTO_SWITCH = 0; //自动切换 public static final int MANUAL_SWITCH = 1; //手动切换 private int mCurSwitchMode = AUTO_SWITCH; private static final int DELAY_TIME = 2000; //自动播放间隔时间 private int mItem = -100; private ViewIndicator mViewIndicator; private Handler mHandler = new Handler(Looper.getMainLooper()); public CustomViewPager(Context context) { this(context, null); }public CustomViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); }private void init() { mPagerAdapter = new MyPagerAdapter(); this.setAdapter(mPagerAdapter); this.addOnPageChangeListener(mPageChangeListener); }private OnPageChangeListener mPageChangeListener = new SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { mCurrentPos = position; if (mCurSwitchMode == AUTO_SWITCH) { mHandler.postDelayed(mAutoSwitchRunnable, DELAY_TIME); } mViewIndicator.setCurrentIndex(position); }@Override public void onPageScrollStateChanged(int state) { Log.d(TAG," THE OPANGE...-->>"+state); switch (state) { case ViewPager.SCROLL_STATE_IDLE: mItem = -100; break; case ViewPager.SCROLL_STATE_DRAGGING: mItem = CustomViewPager.this.getCurrentItem(); mHandler.removeCallbacks(mAutoSwitchRunnable); break; case ViewPager.SCROLL_STATE_SETTLING: int item = CustomViewPager.this.getCurrentItem(); Log.d(TAG,"the item-->>"+item + " the mItem-->>"+ mItem); if (Math.abs(mItem - item) == 1) { mCurSwitchMode = MANUAL_SWITCH; } else if(mCurSwitchMode == AUTO_SWITCH && mItem != -100){ mHandler.postDelayed(mAutoSwitchRunnable, DELAY_TIME); } break; } } }; private Runnable mAutoSwitchRunnable = new Runnable() { @Override public void run() { if (mTotal > 1 && mCurSwitchMode == AUTO_SWITCH) { ++mCurrentPos; int item = (mCurrentPos) % mTotal; if (item == 0) { mCurSwitchMode = MANUAL_SWITCH; return; } CustomViewPager.this.setCurrentItem(item, true); } } }; public void startAutoSwitch() { if (mTotal > 1 && mCurSwitchMode == AUTO_SWITCH) { mHandler.postDelayed(mAutoSwitchRunnable, DELAY_TIME); } }public void setIndicator(ViewIndicator viewIndicator){ mViewIndicator = viewIndicator; mViewIndicator.setIndicatorViewNum(mTotal); }/** * 设置切换模式 * * @param mode */ public void setSwitchMode(int mode) { mCurSwitchMode = mode; }public void reset() { mCurSwitchMode = AUTO_SWITCH; mHandler.removeCallbacks(mAutoSwitchRunnable); }/** * 添加所有子View到ViewPager * * @param views */ public void addAll(List views) { mPagerAdapter.addItemAll(views); mTotal = mPagerAdapter.getCount(); }/** * 添加指定的View到ViewPager * * @param view */ public void add(View view) { mPagerAdapter.addItem(view); mTotal = mPagerAdapter.getCount(); }/** * 添加指定View到指定位置 * * @param view * @param pos */ public void add(View view, int pos) { mPagerAdapter.addItem(view, pos); mTotal = mPagerAdapter.getCount(); }/** * 移除指定View * * @param view */ public void remove(View view) { mPagerAdapter.removeItem(view); mTotal = mPagerAdapter.getCount(); }public void remove(int pos){ mPagerAdapter.removeItem(pos); mTotal = mPagerAdapter.getCount(); }/** * 适配器 */ private class MyPagerAdapter extends PagerAdapter { private List itemViews = new ArrayList<>(); public void addItemAll(List views) { itemViews.clear(); itemViews.addAll(views); this.notifyDataSetChanged(); }public void addItem(View view) { itemViews.add(view); this.notifyDataSetChanged(); }public void removeItem(View view) { itemViews.remove(view); this.notifyDataSetChanged(); }public void removeItem(int pos){ itemViews.remove(pos); this.notifyDataSetChanged(); }public void addItem(View view, int pos) { itemViews.add(pos, view); this.notifyDataSetChanged(); }@Override public int getCount() { //Log.d(TAG,"the count--->>"+itemViews.size()); return itemViews.size(); }@Override public boolean isViewFromObject(View view, Object object) { return view == object; }@Override public Object instantiateItem(ViewGroup container, int position) { Log.d(TAG, "====instantiateItem=== "+position); container.addView(itemViews.get(position)); return itemViews.get(position); }@Override public void destroyItem(ViewGroup container, int position, Object object) { Log.d(TAG, "====destroyItem=== "+ position); container.removeView((View) object); } }}

自定义ViewPagerIndicator
public class ViewPagerIndicator extends FrameLayout { private CustomViewPager mViewPager; private ViewIndicator mViewIndicator; private int mBottomMargin = 40; //指示点距离底部的距离,单位px private int mLeftMargin = 20; //指示点之间的距离,单位pxpublic ViewPagerIndicator(Context context) { this(context, null); }public ViewPagerIndicator(Context context, AttributeSet attrs) { this(context, attrs, 0); }public ViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }private void init() { addViewPager(); addIndicatorView(); }private void addViewPager() { mViewPager = new CustomViewPager(this.getContext()); FrameLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); this.addView(mViewPager, params); }private void addIndicatorView() { mViewIndicator = new ViewIndicator(this.getContext()); FrameLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.BOTTOM | Gravity.CENTER; params.bottomMargin = mBottomMargin; this.addView(mViewIndicator, params); }public void setBottomMargin(int bottomMargin){ mBottomMargin = bottomMargin; } public void setLeftMargin(int leftMargin){ mLeftMargin = leftMargin; }public void addAll(List views){ mViewPager.addAll(views); mViewIndicator.setMarginLeft(mLeftMargin); mViewPager.setIndicator(mViewIndicator); }public void removeItem(int pos){ mViewPager.remove(pos); }public void startAutoPlay(){ mViewPager.startAutoSwitch(); }

以上就实现了带有指示点的ViewPager。
ViewPager实时刷新数据更新View 在项目中遇到过这种需求,需要在不同情况下实时更新ViewPager中View的个数。相信你一开始也是去刷新PagerAdapter适配器,那么同样也会遇到这么一个坑,当你删除适配器中的一个数据时,结果View页面的个数是减少了,但是刚被删除的View页面却还存在,并且重叠在后一个View之上。也就是:
ViewPager刷新适配器导致View重叠,并没有达到更新数据的效果。
导致这个问题的原因是适配器的确刷新了,但是PagerAdapter适配器中的View并没有实时删除。因为你适配器默认没有重写适配器中的如下方法:
public int getItemPosition(Object object) { return POSITION_UNCHANGED; }

默认情况下,并没有重写上面那个方法。该方法的返回值表示View的位置没有改变,即时刷新适配器了,View也不会及时更新。解决办法就是重写上面的方法,返回值改成:POSITION_NONE。即重写PagerAdapter适配器中的
@Override public int getItemPosition(Object object) { return POSITION_NONE; }

  • POSITION_UNCHANGED:更新适配器时,对应位置的View不销毁
  • POSITION_NONE:更新适配器时,对应位置的View会销毁
如此ViewPager就可以实时刷新适配器来更新View界面了。
ViewPager禁止滑动 开发中遇到这种情况,在刷新PagerAdapter适配器时,当ViewPager中只剩下一个View的时候,按理应该ViewPager不可以滑动的。但是你会发现,你还可以滑动出其他删除的页面,只是你滑动不过去,还是可以滑的,这个时候当ViewPager中只有一个View时应该禁止ViewPager的滑动。我的做法是重写ViewPager总的scrollTo方法:
@Override public void scrollTo(int x, int y) { if (mTotal != 1){ super.scrollTo(x, y); } }

当ViewPager中的item总数为1时就不让其执行滑动操作。
ViewPager自动轮播的实现 自动轮播也很简单,只需要在代码中稍加控制即可,这里就直接贴代码了
private Runnable mAutoSwitchRunnable = new Runnable() { @Override public void run() { if (mTotal > 1 && mCurSwitchMode == AUTO_SWITCH) { ++mCurrentPos; int item = (mCurrentPos) % mTotal; CustomViewPager.this.setCurrentItem(item, true); } } }; public void startAutoSwitch() { if (mTotal > 1 && mCurSwitchMode == AUTO_SWITCH) { mHandler.postDelayed(mAutoSwitchRunnable, DELAY_TIME); } }

ViewPager自动切换的速度控制 在自动播放ViwePager的时候发现播放速度太快,看不到一个切换过程,体验不是特别好。我们需要自己控制ViewPager自动切换的速度,但看了ViwePager标准的API都没有暴露出相关的接口。我们只好自己想办法了,在ViewPager里,控制ViewPager切换的速度是有其中的一个mScroller成员变量来控制。而mScroller是Scroller类,那么我们能否将ViewPager中的mScroller替换成我们自定义的Scroller来控制切换速度呢?反射可以做到这一点,利用反射,我们将ViewPager的mScroller成员变量换成我们定义的Scroller。实现代码如下:
/** * Created by xujinping on 2017/3/28. * 由于ViewPager默认切换的速度太慢,体验不好,而又没有暴露出接口,故自己写个类来控制ViewPager自动切换的速度 */public class ViewPagerScroller extends Scroller { private int DEFAULT_DURATION = 900; //mspublic ViewPagerScroller(Context context) { this(context, null); }public ViewPagerScroller(Context context, Interpolator interpolator) { this(context, interpolator, false); }public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) { super(context, interpolator, flywheel); }@Override public void startScroll(int startX, int startY, int dx, int dy) { super.startScroll(startX, startY, dx, dy, DEFAULT_DURATION); }@Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { super.startScroll(startX, startY, dx, dy, DEFAULT_DURATION); }/** * 设置切换一个页面的时长,时间越长,切换速度越慢,反之。 * * @param duration */ public void setDuration(int duration) { DEFAULT_DURATION = duration; }//该方法在ViewPager初始化的时候调用即可 public void initViewPagerScroller(ViewPager viewPager) { try { Field mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); mScroller.set(viewPager, this); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }

以上类暴露出一个方法setDuration,用于控制ViewPager切换的时间,即时间越长,自动切换的速度越慢,反之。如此,你就可以随意控制ViewPager自动切换的速度了。
ViewPager切换动画效果的定制 默认情况下ViewPager切换没有动画效果的,只是简单的scrollTo平移。想要比较炫酷的动画效果就需要自定义了。在ViewPager中有这么一个方法setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer)用于设置ViewPager切换动画。google官方也给出了两个例子:
1、DepthPageTransformer
public class DepthPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // (0,1] // Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }

使用示例:
mViewPager.setPageTransformer(true, new DepthPageTransformer());

效果:
andorid|Android ViewPager的使用总结
文章图片

2、ZoomOutPageTransformer
public class ZoomOutPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.85f; private static final float MIN_ALPHA = 0.5f; @SuppressLint("NewApi") public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); Log.e("TAG", view + " , " + position + ""); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0 { // [-1,1] // Modify the default slide transition to shrink the page as well float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageHeight * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { view.setTranslationX(horzMargin - vertMargin / 2); } else { view.setTranslationX(-horzMargin + vertMargin / 2); }// Scale the page down (between MIN_SCALE and 1) view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); // Fade the page relative to its size. view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }

代码调用示例:
mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());

效果:
andorid|Android ViewPager的使用总结
文章图片

以上示例效果主要是实现ViewPager.PageTransformer接口就有切换动画效果了。我们来看看该接口的定义:
public interface PageTransformer { /** * Apply a property transformation to the given page. * * @param page Apply the transformation to this page * @param position Position of page relative to the current front-and-center *position of the pager. 0 is front and center. 1 is one full *page position to the right, and -1 is one page position to the left. */ void transformPage(View page, float position); }

参数解释:
假设从A页面切换到B页面。
  • View page:当前正在执行动画的View
  • float position:View的位置表示,其取值范围如下:
1.(-∞,-1):表示最左边不可见的View page
2. [-1,0]:表示从A切换到B页面时,A页面的position变化
3. ( 0,1]:表示从A切换到B页面时,B页面的pisition变化
4.(1,+∞):表示最右边不可见的View page
结合上面的参数解析和两个例子,其实实现ViewPager切换动画是有套路得,套路如下:
  1. 当postion 位置(-∞,-1)和(1,+∞)时,将对应位置的View设置为view.setAlpha(0); 即可。
  2. 当postion位置[0,-1]变化时,对应左A页面的动画。
  3. 当postion位置( 1,0]变化时,对应左B页面的动画。
【andorid|Android ViewPager的使用总结】自定义切换动画代码示例如下:
this.setPageTransformer(true, new PageTransformer() { @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <=0){ view.setAlpha(1f); view.setTranslationY(0); }else if (position <=1){ view.setAlpha(1- position); view.setTranslationY(pageHeight * -position); }else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } });

    推荐阅读