真正循环的自定义ViewPager

在实际的开发中常常会用到无限循环的Viewpager,但是Viewpager本身不支持。

参考了其他人的方法,无非就是2种。 

1、viewpager getCount 为int最大值,然后viewpager可以左右不停的滚动,里面是重复的View

2、viewpager 前后各增加一个item   第一个位置和最后一个位置做特殊处理。

这2种方法都可以实现,但是从原理上他是有缺陷的。

我的这种方法是重新模仿ViewPager写一个类似的。 下面是代码

package com.example.demo;

import java.util.ArrayList;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
/****
 * 可以无限循环的ViewPager
 * @author haleyzheng
 *
 */
public class CircledViewPager extends LinearLayout{
	
	private static final String TAG = "CircledViewPager";
	private static final long ANIMATION_FRAME_DURATION = 1000/80;
	private final static float MIN_SPEED = 2.0f;
	private float mMin_speed = MIN_SPEED;
	private int mPosition = 0;
	private XBaseAdapter mAdapter;
	private XBaseAdapter mOldAdapter;
	private ArrayList<ItemInfo> mViewStack = new ArrayList<ItemInfo>();
	private int mIndex= 0;
	private int mSize = 0;
	
	
	private final static int MOVE_LEFT = 10001;
	private final static int MOVE_RIGHT = 10002;
	private final static int MOVE_BACK = 10003;
		
	
	private LayoutParams mLayoutParams ;
	private long mLastAnimationTime = 0;
	private long mCurrentAnimationTime = 0;
	private float mSpeed ;
	private View mCurrentView;
	private VelocityTracker mTracker;
	private boolean mAnimationEnd = true;
	
	private PagerChangedListener listener;
	private DataSetObserver mDataSetObserver;
	private boolean mScaled = false;
	private Handler mHandler = new Handler(){
		public void handleMessage(android.os.Message msg) {
//			Log.d(TAG,"handleMessage  " + msg.what);
			switch (msg.what) {
			case MOVE_LEFT:
				doLeftOrBounceAnimation();
				break;
			case MOVE_RIGHT:
				doRightOrBounceAnimation();
				break;
			}
		};
	};
	
	public CircledViewPager(Context context) {
		this(context,null);
	}

	public CircledViewPager(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public CircledViewPager(Context context, AttributeSet attrs, int defStyle) {
		this(context, attrs);
	}

	private void init(){
		final Context context = getContext();
		mMin_speed = context.getResources().getDisplayMetrics().density * MIN_SPEED;
		mLayoutParams = new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);
		
	}
	
	public void setScaleEnable(boolean scaled){
		if(mAdapter!=null){
			throw new IllegalAccessError(" setScaleEnable() must be called before setAdpter ");
		}
		mScaled = scaled;
		
	}
	public void setAdapter(XBaseAdapter adapter){
		mOldAdapter = mAdapter;
		mAdapter = adapter;
		if(mAdapter!=null){
			mDataSetObserver= new DataSetObserver() {
				@Override
				public void onChanged() {
					mSize = mAdapter.getCount();
					refresh();
				}
				@Override
				public void onInvalidated() {
					invalidate();
				}
			};
			mAdapter.registerDataSetObserver(mDataSetObserver);
		}
		mSize = adapter.getCount();
		refresh();
		
	}
	
	/***
	 * 设置当前项
	 * @param index
	 */
	public void setCurrentItem(final int index){
		mIndex = index;
		refresh();
		pagerMovingEnd(mIndex);
	}
	
	public void setCurrentItem(final int index, boolean smoothScroll){
		if(index == mIndex ){
			return ;
		}
		if(Math.abs(index - mIndex) == 1){
			if(index > mIndex){
				flipLeft();
			}else if(index < mIndex){
				flipRight();
			}
		}else if(mIndex == 0 && index == mViewStack.size()-1 ){
			flipRight();
		}else if(mIndex == mViewStack.size() - 1  && index == 0){
			flipLeft();
		}else if(Math.abs(index - mIndex) > 1){
			
		}
		
	}
	
	
	/**
	 * 设置回调
	 * @param listen
	 */
	public void setPagerChangedListener(PagerChangedListener listen){
		listener = listen;
	}
	
	
	
	
	@Override
	public void requestLayout() {
		super.requestLayout();
	}
	
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d(tag,"dispatchTouchEvent  " + ev.getAction());
		return super.dispatchTouchEvent(ev);
	}
	
	
	private final static String tag = "event";
	private int oldx =-1;
	private int oldy =-1;
	
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d(tag,"onInterceptTouchEvent " + ev.getAction());
		final int action = ev.getAction()&0xff;
		final int x = (int) ev.getX();
		final int y = (int) ev.getY();
//		Log.d(tag,"action  " + action);
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			oldx = x;
			oldy = y;
//			Log.d(tag,"onInterceptTouchEvent aciton Down ");
			return false;
		case MotionEvent.ACTION_MOVE:
//			Log.d(tag,"onInterceptTouchEvent aciton Move ");
			if(y-oldy!=0){
				if((float)Math.abs(x-oldx)/(float)Math.abs(y-oldy)>2.0f){
					return true;
				}else {
					return false;
				}
			}else{
				return false;
			}
		}
		return super.onInterceptTouchEvent(ev);
//		return true;
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d(tag,"onTouchEvent " + event.toString());
		if(!mAnimationEnd){
			return true;
		}
		if(mTracker==null){
			mTracker = VelocityTracker.obtain();
		}
		final int action = event.getAction();
		final int x = (int) event.getX();
		final int y = (int) event.getY();
		if(mCurrentView==null )return false;
		mPosition = mCurrentView.getLeft();
		switch (action & 0xff) { 
			case MotionEvent.ACTION_MOVE:{
				mTracker.addMovement(event);
				mTracker.computeCurrentVelocity(1000);
				move(x-oldx);
				oldx = x;
				break;
			}
			case MotionEvent.ACTION_UP:{
				oldx = -1;
				oldy = -1;
				mPosition = mCurrentView.getLeft();
				prepareToAnimation();
				break;
			}
			case MotionEvent.ACTION_CANCEL:{
				oldx = -1;
				oldy = -1;
				prepareToAnimation();
				mPosition = mCurrentView.getLeft();
				break;
			}
				
		}
		return true;
	}
//	
		
	private void move(int dis){
		Log.d(tag," move  position " + mPosition + "    dis " + dis);
		if(mPosition== 0&&dis!=0){
			pagerChanged(mIndex, dis<0?(mIndex+1>=mSize?mIndex+1-mSize:mIndex+1):(mIndex-1<0?mIndex+mSize-1:mIndex-1));
		}
		mPosition+=dis;
		final float movedegreepreView = (float)mPosition/(float)getMeasuredWidth();
		final float movedegree = (float)(mPosition*(mIndex+1))/(float)(getMeasuredWidth()*mAdapter.getCount());
		pagerMoving(movedegreepreView, movedegree);
		mCurrentView.offsetLeftAndRight(mPosition-mCurrentView.getLeft());
		if(mScaled){
			for(int i = 0; i < mViewStack.size(); ++i){
//				Log.d(TAG," mViewStack.get(i).view  " + mViewStack.get(i).view.getClass().getSimpleName());
				final ItemView item  = (ItemView) mViewStack.get(i).view;
				item.setPosition(mPosition);
			}
		}
		invalidate();
	}
	private void prepareToAnimation(){
		final long now  = SystemClock.uptimeMillis();
		mAnimationEnd  = false;
//		Log.d(TAG,"prepareTOanimation   " + now);
		mLastAnimationTime = now;
		mCurrentAnimationTime = now;
		if(mTracker!=null){
			 float speed = mTracker.getXVelocity();
			mSpeed = speed/1000;
			if(mSpeed<mMin_speed)mSpeed=mMin_speed;
			if(speed > 0){
				mSpeed  = Math.abs(mSpeed);
				doRightOrBounceAnimation();
			}else{
				mSpeed = -1*Math.abs(mSpeed);
				doLeftOrBounceAnimation();
			}
		}
	}
	
	public void flipLeft(){
		mPosition = mCurrentView.getLeft();
		move(-1);
		final long now = SystemClock.uptimeMillis();
		mAnimationEnd = false;
		mLastAnimationTime = now;
		mCurrentAnimationTime = now;
		mSpeed = -mMin_speed;
		doLeftOrBounceAnimation();
	}
	public void flipRight(){
		mPosition = mCurrentView.getLeft();
		move(1);
		final long now = SystemClock.uptimeMillis();
		mAnimationEnd  = false;
		mLastAnimationTime = now;
		mCurrentAnimationTime = now;
		mSpeed = mMin_speed;
		doRightOrBounceAnimation();
	}
	
	private void doRightOrBounceAnimation(){
//		Log.d(TAG,"doRightAnimation " );
		final long t = ANIMATION_FRAME_DURATION;//(now -mLastAnimationTime);
		int s = (int) (mSpeed*t);
		if(mCurrentView.getRight()> getMeasuredWidth()){
			if(mCurrentView.getLeft()==getMeasuredWidth()){
				///动画结束
//				Log.d(TAG,"right Animation End ");
				mHandler.removeMessages(MOVE_RIGHT);
				endRightanimation();
				return ;
			}
			if(s+mCurrentView.getLeft()>getMeasuredWidth()){
				s = getMeasuredWidth()-mCurrentView.getLeft();
			}	
		}else{
			if(mCurrentView.getLeft()>=0){
//				Log.d(TAG,"from right back animation end  ");
				mHandler.removeMessages(MOVE_RIGHT);
				endBounceanimtion();
				return;
			}
			if(s+mCurrentView.getLeft()>0){
				s = -mCurrentView.getLeft();
			}
		}
		
		move(s);
		mCurrentAnimationTime+=ANIMATION_FRAME_DURATION;
		mHandler.removeMessages(MOVE_LEFT);
		mHandler.removeMessages(MOVE_RIGHT);
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MOVE_RIGHT), ANIMATION_FRAME_DURATION);
	}
	private void doLeftOrBounceAnimation() {
		Log.d(TAG,"doleftAnimation  " + mPosition);
		final long t = ANIMATION_FRAME_DURATION;//(now - mLastAnimationTime)/1000;
		int s = (int)(mSpeed*t);
		if(mCurrentView.getRight()<getMeasuredWidth()){
			if(mCurrentView.getRight()==0){
				mHandler.removeMessages(MOVE_LEFT);
				endLEftAnimation();
				return;
			}
			if(s+mCurrentView.getRight()<0){
				s= -mCurrentView.getRight();
			}
		}else{
			if(mCurrentView.getLeft()==0){
				mHandler.removeMessages(MOVE_LEFT);
				endBounceanimtion();
				return ;
				
			}
			if(s+mCurrentView.getLeft()<0){
				s= -mCurrentView.getLeft();
			}
			
		}
		move(s);
		mCurrentAnimationTime+=ANIMATION_FRAME_DURATION;
		mHandler.removeMessages(MOVE_LEFT);
		mHandler.removeMessages(MOVE_RIGHT);
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MOVE_LEFT), ANIMATION_FRAME_DURATION);
	}
	private void endBounceanimtion(){
//		Log.d(TAG,"endBounceAnimation  ");
		mAnimationEnd = true;
		pagerMovingEnd(mIndex);
	}
	private void endRightanimation(){

		int index = mIndex-1;
		if(index<0){
			index = mAdapter.getCount()+index;
		}
		setCurrentItem(index);
		mAnimationEnd = true;

	}
	private void endLEftAnimation(){
		Log.d(TAG,"endLeftAnimation ");
		int index = mIndex+1;
		index = index%mAdapter.getCount();
		setCurrentItem(index);
		mAnimationEnd = true;	
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		Log.d(TAG,"onMeasure ");
		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int height = MeasureSpec.getSize(heightMeasureSpec);
		for(int i=0; i<mViewStack.size(); ++i){
			View child = mViewStack.get(i).view;
			measureChild(child, width+MeasureSpec.EXACTLY, height+MeasureSpec.EXACTLY);
		}
		final int widthmode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightmode = MeasureSpec.getMode(heightMeasureSpec);
		setMeasuredDimension(width+widthmode,height+heightmode);
	}
	
	/****
	 * 只有左上的padding会有效果
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
//		Log.d(TAG,"onLayout");
		final int width  = r-l;
		final int height = b-t;
		Log.d(TAG,"onLayout viewStackSize " + mViewStack.size());
		for(int i = 0; i < mViewStack.size(); ++i){
			final View view = mViewStack.get(i).view;
			int pos = mViewStack.get(i).index;
			final int gap = pos-mIndex;
			final int viewleft = view.getPaddingLeft()+ (width-view.getMeasuredWidth())/2;
			final int viewtop = getPaddingTop() + (height-view.getMeasuredHeight())/2;
			view.layout(viewleft+gap*width, viewtop, viewleft+gap*width+view.getMeasuredWidth(), viewtop+view.getMeasuredHeight());
		}
		
	}
	
	
	@Override
	protected void dispatchDraw(Canvas canvas) {
//		Log.d(TAG,"dispatchDraw "  +mPosition);
		final long drawingTime = getDrawingTime();
		

		for(int i = 0; i < mViewStack.size(); ++i){
			View child = mViewStack.get(i).view;
			if(child.equals(mCurrentView)){
				drawChild(canvas, child, drawingTime);
			}else{
				canvas.save();
				canvas.translate(mCurrentView.getLeft(), 0);
				drawChild(canvas, child, drawingTime);
				canvas.restore();
			}
		}
	}
	
	
	private void refresh(){
		if(mAdapter!=null ){
			if((mIndex>=0 && mIndex < mAdapter.getCount())){
				preRemove();
				preLoad(mIndex);
				for(int i = 0; i < mViewStack.size(); ++i){
					final View view = mViewStack.get(i).view;
					final int index = mViewStack.get(i).index ;
					if(index ==mIndex){
						mCurrentView = view;
					}
					addView(view,mLayoutParams);
				}
			}else if(mAdapter.getCount() ==0 && mIndex==0){
				return ;
			}else{
				throw new IndexOutOfBoundsException("current index is " + mIndex +"  size is  "+ mAdapter.getCount());
			}
			
		}
	}
	
	private void preRemove(){
		for(int i=0; i<mViewStack.size();++i){
			final View view = mViewStack.get(i).view;
			final int position = mViewStack.get(i).index;
			if(mScaled){
				((ViewGroup)view).removeAllViews();
			}
			removeView(view);
			mAdapter.destoryItem(position, this);
		}
	}
	private void preLoad(final int position){
		if(mAdapter!=null){
			mViewStack.clear();
			mViewStack.add(preLoadPreView(position));
			mViewStack.add(loadView(position));
			mViewStack.add(preLoadNextView(position));
		}
	}
	private ItemInfo preLoadPreView(final int position){
		int pos = position -1;
		
		return loadView(pos);
	}
	private ItemInfo preLoadNextView(final int position){
		int pos = position +1;
		
		return loadView(pos);
	}
	private ItemInfo loadView(final int index){
		if(mAdapter == null){
			throw new NullPointerException("adapter is null");
		}
		int pos = index;
		if(pos>=mSize)pos=0;
		if(pos<0)pos+=mSize;
		View view = mAdapter.initItem(pos, this);
		if(mScaled){
			final ItemView item = new ItemView(getContext());
			item.addView(view);
			view.requestLayout();
			return new ItemInfo(item,index,pos);
		}else{
			view.requestLayout();
			return new ItemInfo(view,index,pos);
		}
	}
	
	protected class ItemInfo{
		View view;
		int index;
		int position;
		public ItemInfo(View v, int i, int pos) {
			view = v;
			index = i;
			position = pos;
		}
	}
	
	private void pagerChanged( final int position, final int targetPosition){
		if(listener!=null){
			listener.onPagerChanged(position, targetPosition);
		}
	}
	private void pagerMoving(final float movedegreepreView, final float movedegree){
		if(listener!=null){
			listener.onPagerMoving(-1*movedegreepreView, -1*movedegree);
		}
	}
	private void pagerMovingEnd(final int position){
		if(listener!=null){
			listener.onPagerMovingEnd(position);
		}
	}
	
	public interface PagerChangedListener{
		/***
		 * 开始滑动, targetPosition代表动画停止的位置,但是如果中途动画改变则不是正确的
		 * @param position
		 * @param targetPosition
		 */
		public void onPagerChanged(final int position, final int targetPosition);
		public void onPagerMoving(final float movedegreepreView, final float movedegree);
		public void onPagerMovingEnd(final int position);
	}
	
	private class ItemView extends FrameLayout {
		private final static String Tag = "ItemView";
		public ItemView(Context context, AttributeSet attrs, int defStyle) {
			super(context, attrs, defStyle);
			// TODO Auto-generated constructor stub
		}

		public ItemView(Context context, AttributeSet attrs) {
			super(context, attrs);
			// TODO Auto-generated constructor stub
		}


		private int mPosition =0;

		public ItemView(Context context) {
			super(context);
			// TODO Auto-generated constructor stub
		}

		private Camera mCamera = new Camera();
		private Matrix mMatrix = new Matrix();

		private void scale(Canvas canvas, int position) {
			int pos = Math.abs(position);
			canvas.save();
			mCamera.save();
			float degree = 0.0f;
			if (pos >3 * getMeasuredWidth() / 4) {
				degree = Math.abs((float)(getMeasuredWidth()-pos)/((float)getMeasuredWidth()/4));
			}else if(pos < getMeasuredWidth()/4){
				degree = Math.abs((float)pos/((float)getMeasuredWidth()/4));
			}else{
				degree = 1.0f;
			}
			Log.d(Tag,"degree " + degree );
			final float scale = degree * 100;
			Log.d(Tag, "scale     " + scale + "     position   " + position
					+ "  pos " + pos + " getMeasureWidth " + getMeasuredWidth());
			mCamera.translate(0, 0, scale);
			mCamera.getMatrix(mMatrix);
			mMatrix.preTranslate(-(getMeasuredWidth() / 2),
					-(getMeasuredHeight() / 2));
			mMatrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
			mCamera.restore();
			canvas.concat(mMatrix);
		}

		protected void setPosition(int pos) {
			mPosition = pos;
			invalidate();
		}

		@Override
		protected void dispatchDraw(Canvas canvas) {
			scale(canvas, mPosition);
			super.dispatchDraw(canvas);
			canvas.restore();
		}

	}
	
}
下面是 Adapter , Adapter 也借鉴了 PagerAdapter


package com.example.demo;

import java.util.ArrayList;

import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.View;

public abstract class XBaseAdapter {
	private DataSetObservable mDataSetObservable = new DataSetObservable();
	private ArrayList<ViewInfo> mViewCache = new ArrayList<ViewInfo>();
	private boolean mShouldCache = false;

	public XBaseAdapter() {
	}

	/************************************* 不要调用这些方法 **********************************************/

	void registerDataSetObserver(DataSetObserver observer) {
		mDataSetObservable.registerObserver(observer);
	}

	void unregisterDataSetObserver(DataSetObserver observer) {
		mDataSetObservable.unregisterObserver(observer);
	}


	/**********************************************************************************************/

	public void notifyDataSetChanged() {
		mDataSetObservable.notifyChanged();
	}
	
	public void notifyDataSetInvalidated() {
		mDataSetObservable.notifyInvalidated();
	}
	/***
	 * 设置缓存
	 * 
	 * @param enable
	 */
	public void setCacheEnable(boolean enable) {
		mShouldCache = enable;
		if (!mShouldCache) {
			mViewCache.clear();
		}
	}

	public abstract int getCount();

	@Deprecated
	public abstract Object getItem(final int position);

	public abstract void destoryItem(final int position, final View container);

	public abstract View instantiateItem(final int position,
			final View container, final View contentView);

	final View initItem(final int position, final View container) {
		View view = null;
		if (mShouldCache) {
			for (int i = 0; i < mViewCache.size(); ++i) {
				ViewInfo info = mViewCache.get(i);
				if (info != null && info.position == position) {
					view = info.view;
					if (view != null) {
						return this.instantiateItem(position, container, view);
					}
				}
			}
			if (view == null) {
				view = this.instantiateItem(position, container, null);
			}
			mViewCache.add(new ViewInfo(view, position));
		}
		if (view == null) {
			view = this.instantiateItem(position, container, null);
		}
		return view;
	}
	/**
	 * unuseful methods currently
	 */
	@Deprecated
	public  void beginUpdata(){}

	/**
	 * unuseful methods currently
	 */
	@Deprecated
	public  void finishUpdata(){}

	private class ViewInfo {
		View view;
		int position;

		public ViewInfo(View v, int pos) {
			view = v;
			position = pos;
		}
	}
}

现阶段这个控件只能支持3个和3个以上的item,小于的话还是有问题,另大家有疑问可以直接问我哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值