在实际的开发中常常会用到无限循环的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,小于的话还是有问题,另大家有疑问可以直接问我哦