赞
踩
之前有一个项目中有用到轮播,不过不是简单的轮播图片就完了,而是要轮播很多个View,一开始我的想法和大家一样在github在一个算了,哈哈,不过在试用了很多个项目之后都觉得不能完全满足我的需求,大部分还是针对于图片轮播的场景,所以是时候自己搞一个既支持图片,也支持各种自己定义的View,同时也可以选择不同实现方式的指示器或者干脆去掉,适应个各种需求场景。
这就是他的效果,下面先源码讲解先。
LoopViewPager是这个库的关键类,其内部最基本的实现类其实还是android自带的ViewPager,代码如下:
public void initViewPage(Context context){ mHandler=new Handler(); this.viewPager=new ViewPager(context); this.viewPager.setOffscreenPageLimit(2); loopViewPagerScroller = new LoopViewPagerScroller(context); loopViewPagerScroller.setScrollDuration(2000); loopViewPagerScroller.initViewPagerScroll(viewPager); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { viewPager.setId(viewPager.hashCode()); } else { viewPager.setId(View.generateViewId()); } loopRunnable=new Runnable() { @Override public void run() { viewPager.setCurrentItem(currentItem); currentItem++; mHandler.postDelayed(loopRunnable,delayTime); } }; viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if(onPageChangeListener!=null){ onPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels); } if(indicatorCanvasView!=null){ indicatorCanvasView.onPageScrolled(position,positionOffset,positionOffsetPixels); } } @Override public void onPageSelected(int position) { currentItem=position; if(onPageChangeListener!=null){ onPageChangeListener.onPageSelected(position); } if(indicatorView!=null){ indicatorView.changeIndicator(position==viewNumber+1?0:(position-1)); } } @Override public void onPageScrollStateChanged(int state) { final int position = viewPager.getCurrentItem(); if(onPageChangeListener!=null){ onPageChangeListener.onPageScrollStateChanged(state); } if(state==ViewPager.SCROLL_STATE_IDLE){ if(position==0){ loopViewPagerScroller.setSudden(true); viewPager.setCurrentItem(viewNumber,true); loopViewPagerScroller.setSudden(false); }else if(position==(viewNumber+1)){ loopViewPagerScroller.setSudden(true); viewPager.setCurrentItem(1,true); loopViewPagerScroller.setSudden(false); } } } }); this.addView(this.viewPager); }
在这里我们知道,LoopViewPager里面其实最主要就是包裹着ViewPage而已,至于指示器后面在讲。那么一个简单的ViewPage是怎么实现无限轮播的呢,关键setData()方法里,如下代码:
public void setData(Context context, List<T> mData, CreateView mCreatView){
viewNumber=mData.size();
initIndicator(getContext());
LoopViewPagerAdapter loopViewPagerAdapter=
new LoopViewPagerAdapter(context,mData,mCreatView,onClickListener);
viewPager.setAdapter(loopViewPagerAdapter);
viewPager.setCurrentItem(1);
}
在上面的代码里有关键的类,是LoopViewPagerAdapter,实现的是View的无限轮播,有这个基础类,基本就可以为所欲为了
这是针对View其中包括ImageView的轮播的,代码如下:
public class LoopViewPagerAdapter<T> extends PagerAdapter { private OnPageClickListener onClickListener; private CreateView mCreateView; private Context context; private List<T> mData; public LoopViewPagerAdapter(Context context, List<T> list, CreateView createView, OnPageClickListener onClickListener){ this.onClickListener=onClickListener; this.mCreateView=createView; this.context=context; this.mData=list; } @Override public Object instantiateItem(ViewGroup container, int position) { position=getActualPosition(position); if(mCreateView==null){ return new View(context); } View view=mCreateView.createView(position); final int finalPosition = position; view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(onClickListener!=null){ onClickListener.onClick(view, finalPosition); } } }); ViewParent vp = view.getParent(); if (vp != null) { ViewGroup parent = (ViewGroup)vp; parent.removeView(view); } mCreateView.updateView(view,position,mData.get(position)); container.addView(view); return view; } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { container.removeView((View)object); mCreateView.deleteView(position); } private int getActualPosition(int position) { if (position == 0) { return this.mData.size()-1; } else if(position==mData.size()+1){ return 0; } else { return position-1; } } @Override public int getCount() { return mData!=null&&!mData.isEmpty()?mData.size()+2:0; } @Override public boolean isViewFromObject(@NonNull View arg0, @NonNull Object arg1) { return arg0==arg1; } }
这里在getCount()方法里返回mData.size()+2个数量,在getActualPosition()返回的是正确的position位置,当postion等于0的时候,返回的是最后一个View的位置,那个就会显示最后一个View,当position等于mData.size()+1时,返回的是第一个View,也就是说在第一个View左边加最后一个View,在最后一个View右边加第一个View,这样就可以做到首尾无缝连接,不过这样是不够的,在上面的initViewPage()方法里有如下代码:
if(state==ViewPager.SCROLL_STATE_IDLE){
if(position==0){
loopViewPagerScroller.setSudden(true);
viewPager.setCurrentItem(viewNumber,true);
loopViewPagerScroller.setSudden(false);
}else if(position==(viewNumber+1)){
loopViewPagerScroller.setSudden(true);
viewPager.setCurrentItem(1,true);
loopViewPagerScroller.setSudden(false);
}
}
在返回的postion==0是最后一个View然后用viewPager.setCurrentItem()调整到最后一个的真实位置,当viewNumber+1是第一个View,通过viewPager.setCurrentItem()调整到第一个View的真实位置。这样就做到无限循环。
有了上面这个类就可以实现View的循环轮播。
讲完轮播,接着就是指示器,指示器我也写了两个,一种是简单的IndicatorView,没什么动画,直接图片切换,一种是实现指示器滑动动画的IndicatiorCanvasView。
先讲简单的指示器,代码如下:
public class IndicatorView extends LinearLayout { private Context context; private int loopNowIndicatorImg; private int loopIndicatorImg; private IndicatorAnimator indicatorAnimator; public IndicatorView(Context context, int loopNowIndicatorImg, int loopIndicatorImg, IndicatorAnimator indicatorAnimator) { this(context,null); this.loopNowIndicatorImg=loopNowIndicatorImg; this.loopIndicatorImg=loopIndicatorImg; this.indicatorAnimator=indicatorAnimator; } public IndicatorView(Context context, @Nullable AttributeSet attrs) { this(context,attrs,0); } public IndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context=context; setOrientation(HORIZONTAL); } public void initView(int viewSize){ for(int i=0;i<viewSize;i++){ ImageView imageView=new ImageView(context); LayoutParams layoutParams=new LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.gravity= Gravity.CENTER; imageView.setLayoutParams(layoutParams); if(i==0){ imageView.setImageResource(this.loopNowIndicatorImg); }else{ imageView.setImageResource(this.loopIndicatorImg); } addView(imageView); } } public void changeIndicator(int select){ if(getChildCount()==0){ return; } for(int i=0;i<getChildCount();i++){ ((ImageView)getChildAt(i)).setImageResource(this.loopIndicatorImg); } ImageView imageView=(ImageView)getChildAt(select); imageView.setImageResource(this.loopNowIndicatorImg); if(this.indicatorAnimator!=null){ indicatorAnimator.indicatorView(imageView); } } }
这是很简单的指示器,首先集成LinearLayout,在通过initView()遍历ImageView,再通过addView添加,这就完成了指示器界面初始化。当ViewPage每滑动一次都会调用changeIndicator()方法,这里先遍历把所有的View都设为未选择状态,再把选中的ImageView设为选中的图片就行了,每什么说的。
public class IndicatiorCanvasView extends LinearLayout { private int select_origin; private float positionOffsetData; private Bitmap originBitmap; private ImageView firstView; private ImageView secondView; private Context context; private int numView; private int[] firstViewLocation=new int[2]; private int[] secondViewLocation=new int[2]; private int originMargin=0; public IndicatiorCanvasView(Context context,int origin,int select_origin) { this(context,null); originBitmap=BitmapFactory.decodeResource(context.getResources(), origin); this.select_origin=select_origin; this.context=context; } public IndicatiorCanvasView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public IndicatiorCanvasView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { firstView.getLocationInWindow(firstViewLocation); secondView.getLocationInWindow(secondViewLocation); originMargin=secondViewLocation[0]-firstViewLocation[0]; } }); } public void initView(int size){ this.numView=size; for(int i=0;i<size;i++){ ImageView originView=new ImageView(context); LayoutParams layoutParams=new LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.gravity= Gravity.CENTER; originView.setLayoutParams(layoutParams); originView.setImageResource(select_origin); if(i==0){ firstView=originView; }else if(i==1){ secondView=originView; } addView(originView); } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); canvas.translate(this.positionOffsetData,0); canvas.drawBitmap(originBitmap,0,0,new Paint()); } public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){ int num=position%this.numView; this.positionOffsetData=(num*originMargin)+originMargin*positionOffset; invalidate(); } }
首先initView()方法还是和之前一样,遍历ImageView再addView();重头戏在于当ViewPage滑动时会回调onPageScrolled()方法,而positionOffset是他的滑动比例,originMargin是两个指示点的距离,而originMargin是怎么算的能,如下代码:
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
firstView.getLocationInWindow(firstViewLocation);
secondView.getLocationInWindow(secondViewLocation);
originMargin=secondViewLocation[0]-firstViewLocation[0];
}
});
既拿到第一个指示点和第二个指示点的位置,然后相减,就是两点之间的间距。在通过
(numoriginMargin)+originMarginpositionOffset拿到滑动的距离,调invalidate()方法刷新。
刷新是会回调:dispatchDraw(Canvas canvas)方法。
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.translate(this.positionOffsetData,0);
canvas.drawBitmap(originBitmap,0,0,new Paint());
}
计算出来的值通过canvas.translate()移动canvas原点,这你在我自定义的文章见多了吧,再通过canvas.drawBitmap()动态画出移动的点。这就实现了点的动画。
这基本就是整个循坏Banner的所有重点。这个Banner既支持普通的View,当然也有懒人专用的传个数组就可实现图片轮播,整个项目我已经生产一个库,具体的源码和用法,怎么引用请参见github.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。