现在的位置: 首页 > 移动开发 > 正文

android 自定义ListView实现元素前移后退的动画特效

2019年09月13日 移动开发 ⁄ 共 13642字 ⁄ 字号 评论关闭

通过自定义ListView实现元素前移后退的动画特效,被点击元素A向前移,A之前元素往后移动.

具体的动画效果如下(效果图是丑了点,有录制gif图片软件请推荐一下吧):


重点在于动画的,具体代码如下:

package com.open.widget;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;

/**
 * 
 * @author yanglonghui
 *
 */
public class HorImageListView extends View {

	private int current_OffsetX=0;//当前屏X轴的偏移量
	private int cuurent_OffsetY=0;//当前屏Y轴的偏移量
	private int moving_OffsetX=0;//当前S手势X轴偏移量
	private int moving_OffsetY=0;//当前S手势Y轴偏移量
	private int current_Page=1;//当前页码,开始页码为1
	private int portraitNumberPerScreen;//每屏头像个数
	private int charHeight=0;//一个字的高度
	private int maxPage=1;//最大页码
	
	private int current_foucsIndex=0;//当前焦点
	private int current_longPressIndex=-1;//当前长按焦点
	private int current_clickIndex=0;//当前点击
	
	private int headWidth=0;//头像宽度
	private int paddingLeft;//左边距
	private Bitmap []bitmapArray;//头像数组
	private Rect headRectArray[]=null;//头像位置
	private Rect drawingRect=new Rect();

    private Bitmap mCircleBitmap=null;
    private PorterDuffXfermode xfermode=new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);
    private PaintFlagsDrawFilter pdf=new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
	private Paint paint = new Paint();
    {
	    paint.setStyle(Paint.Style.STROKE);
	    paint.setFlags(Paint.ANTI_ALIAS_FLAG);
	    paint.setAntiAlias(true);// 设置画笔的锯齿效果。 true是去除,大家一看效果就明白了 
    }
    
    private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG |
            Canvas.CLIP_SAVE_FLAG |
            Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
            Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
            Canvas.CLIP_TO_LAYER_SAVE_FLAG;
    
    private IHeadClick headClickListener;
    
    private Handler mHandler=new Handler();
    private ArrayList<String> headList=new ArrayList<String>();
    
	public HorImageListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

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

	public HorImageListView(Context context) {
		super(context);
		init();
	}
	
	private void init()
	{
		try {  
            if(android.os.Build.VERSION.SDK_INT>=11)  
            {  
                setLayerType(LAYER_TYPE_SOFTWARE, null);  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
		
		mGestureDetector=new GestureDetector(new CusGestureListener());
		setLongClickable(true); 
		setOnTouchListener(onTouchListener);
	}
	
	public void setAdapter(ArrayList<String> headList)
	{
		this.headList=headList;
		current_Page=1;
		current_OffsetX=0;
		current_clickIndex=0;
		current_foucsIndex=0;
		current_longPressIndex=-1;
		
		if(headWidth==0)
		{
			headWidth=DensityUtil.dip2px(getContext(), 60);
		}
		mCircleBitmap=WindowMgr.getInstance().getCircleBitmap(headWidth, headWidth);
		bitmapArray=WindowMgr.getInstance().getAllBitmaps(headList);
		headRectArray=new Rect[bitmapArray.length];
		for(int i=0;i<headRectArray.length;i++)//随时滑动
		{
			headRectArray[i]=new Rect();
		}
		requestLayout();
		invalidate();
	}
	
	private void calculate()
	{
		if(getMeasuredWidth()<=0)
		{
			return;
		}
		portraitNumberPerScreen=getMeasuredWidth()/headWidth;
		portraitNumberPerScreen--;//少一个元素,使不会那么拥挤
		paddingLeft=(int)((float)(getMeasuredWidth()-portraitNumberPerScreen*headWidth)/(float)(portraitNumberPerScreen+1)+0.5f);
		
		if(bitmapArray.length>portraitNumberPerScreen)
		{
			maxPage=(bitmapArray.length%portraitNumberPerScreen==0)?bitmapArray.length/portraitNumberPerScreen:this.bitmapArray.length/portraitNumberPerScreen+1;
		}
		else
		{
			maxPage=1;
		}
		int left = 0;
		int top = DensityUtil.dip2px(getContext(), 10);
		int right = 0;
		int bottom = top+headWidth;
		
		for(int i=0;i<maxPage;i++)//分页效果
		{
			int pageWidthPadding=i*getMeasuredWidth();
			for(int j=0;j<portraitNumberPerScreen&&(i*portraitNumberPerScreen+j)<bitmapArray.length;j++)
			{
				left=pageWidthPadding+paddingLeft*(j+1)+j*headWidth;
				right=left+headWidth;
				headRectArray[i*portraitNumberPerScreen+j].set(left, top, right, bottom);
			}
		}
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int height=DensityUtil.dip2px(getContext(), 80);
		setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); 
		calculate();
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		if(null!=bitmapArray)
		{
			if(current_clickIndex>0)
			{
					canvas.setDrawFilter(pdf);
					for(int i=0;i<bitmapArray.length;i++)
					{
						drawingRect.left=headRectArray[i].left+current_OffsetX+clickOffsetBackDx;
						drawingRect.top=headRectArray[i].top+cuurent_OffsetY;
						drawingRect.right=headRectArray[i].right+current_OffsetX+clickOffsetBackDx;
						drawingRect.bottom=headRectArray[i].bottom+cuurent_OffsetY;
						
						if(i>current_clickIndex)//还需要优化
						{
							if(current_Page==1)
							{
								drawingRect.left=headRectArray[i].left+current_OffsetX;
								drawingRect.top=headRectArray[i].top+cuurent_OffsetY;
								drawingRect.right=headRectArray[i].right+current_OffsetX;
								drawingRect.bottom=headRectArray[i].bottom+cuurent_OffsetY;
							}
							else if(current_Page>1)
							{
								
							}
						}
						
						if(drawingRect.right<getLeft())
						{
							continue;
						}
						else if(drawingRect.left>getRight())
						{
							break;
						}
						
						if(current_clickIndex!=i)
						{
							//绘制头像
					        canvas.drawBitmap(mCircleBitmap, null, drawingRect, paint);
					        paint.setXfermode(xfermode);
					        canvas.drawBitmap(bitmapArray[i], null, drawingRect, paint);
					        paint.setXfermode(null);
					        
						}
					}
					
					drawingRect.left=headRectArray[0].left+clickOffsetforwarDx;
					drawingRect.top=headRectArray[0].top;
					drawingRect.right=headRectArray[0].right+clickOffsetforwarDx;
					drawingRect.bottom=headRectArray[0].bottom;
					
	                int sc = canvas.saveLayer(drawingRect.left, drawingRect.top, drawingRect.right, drawingRect.bottom, null,LAYER_FLAGS);
					//绘制头像
			        canvas.drawBitmap(mCircleBitmap, null, drawingRect, paint);
			        paint.setXfermode(xfermode);
			        canvas.drawBitmap(bitmapArray[current_clickIndex], null, drawingRect, paint);
			        paint.setXfermode(null);
			        
			        canvas.restoreToCount(sc);
			}
			else
			{
					canvas.setDrawFilter(pdf);
					for(int i=0;i<bitmapArray.length;i++)
					{
						drawingRect.left=headRectArray[i].left+current_OffsetX+moving_OffsetX;
						drawingRect.top=headRectArray[i].top+cuurent_OffsetY;
						drawingRect.right=headRectArray[i].right+current_OffsetX+moving_OffsetX;
						drawingRect.bottom=headRectArray[i].bottom+cuurent_OffsetY;
						
						if(drawingRect.right<getLeft())
						{
							continue;
						}
						else if(drawingRect.left>getRight())
						{
							break;
						}
						
						if(current_longPressIndex==i)
						{
							int insetDx=(int)((float)drawingRect.height()/(float)8);
							drawingRect.inset(insetDx, insetDx);
						}
						
						//绘制头像
				        canvas.drawBitmap(mCircleBitmap, null, drawingRect, paint);
				        paint.setXfermode(xfermode);
				        canvas.drawBitmap(bitmapArray[i], null, drawingRect, paint);
				        paint.setXfermode(null);
					}
			}
		}
	}
	
	private OnTouchListener onTouchListener=new OnTouchListener() {
		
		@Override
		public boolean onTouch(View v, MotionEvent event) {
			boolean isConsumed =mGestureDetector.onTouchEvent(event);
			if (isConsumed) return true;
			
			if(event.getAction()==MotionEvent.ACTION_CANCEL||event.getAction()==MotionEvent.ACTION_UP)
			{
				//----分页代码
				int direct=0;//往左边为1,右边为-1,保持不变为0
				if(moving_OffsetX>getMeasuredWidth()/2)//向右边滑过半屏
				{
					current_Page--;
					direct=1;
					if(current_Page<1)
					{
						current_Page=1;
						direct=0;
					}
				}
				else if(moving_OffsetX<-getMeasuredWidth()/2)//向左边滑过半屏
				{
					current_Page++;
					direct=-1;
					if(current_Page>maxPage)
					{
						current_Page=maxPage;
						direct=0;
					}
				}
				
				int old=current_OffsetX+moving_OffsetX;
				int newX=current_OffsetX+direct*getMeasuredWidth();
				if(direct!=0)
				{
					current_foucsIndex=(current_Page-1)*portraitNumberPerScreen;
					
					if(null!=headClickListener)
					headClickListener.onItemClick(current_foucsIndex);
				}
				
Log.v("dx:"+current_OffsetX, "----------------------");
				
				if(current_longPressIndex!=-1)
				{
					invalidate();
					current_longPressIndex=-1;//恢复
					return false;
				}
				else
				{
					mHandler.post(new SmoothRunnable(old, newX));
				}
				return false;
			}
			
			invalidate();
			return isConsumed; 
		}
	};
	
	private GestureDetector mGestureDetector;
	private class CusGestureListener extends SimpleOnGestureListener
	{
		private Rect mDragRect=new Rect();
		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			Log.v("CusGestureListener", "onSingleTapUp");
			
			int x=(int) e.getX();
			int y=(int) e.getY();
			
			boolean isInner=false;
			Rect mRect=new Rect();
			for(int i=0;i<bitmapArray.length;i++)
			{
				mRect.left=headRectArray[i].left+current_OffsetX+moving_OffsetX;
				mRect.top=headRectArray[i].top+cuurent_OffsetY;
				mRect.right=headRectArray[i].right+current_OffsetX+moving_OffsetX;
				mRect.bottom=headRectArray[i].bottom+cuurent_OffsetY;
				
				if(!isInner)
				{
					if(mRect.top<=y&&mRect.bottom>=y)
					{
						isInner=true;
					}
					else
					{
						break;
					}
				}
				
				if(mRect.contains(x, y))
				{
					current_foucsIndex=i;
					current_clickIndex=i;
					
					//动画:被点击向前移动,未点击向后移动
					if(null!=mClickRunnable)
					{
						mClickRunnable.stop();
					}
					mClickRunnable=new ClickRunnable(current_clickIndex);
					mHandler.post(mClickRunnable);
				}
			} 
			return super.onSingleTapUp(e);
		}

		@Override
		public void onLongPress(MotionEvent e) {
//			Log.v("CusGestureListener", "onLongPress");
			
			int x=(int) e.getRawX();
			int y=(int) (e.getRawY()-WindowMgr.getInstance().getStatusBarHeight(getContext()));
			
			Rect mRect=new Rect();
			for(int i=0;i<bitmapArray.length;i++)
			{
				mRect.left=headRectArray[i].left+current_OffsetX+moving_OffsetX;
				mRect.top=headRectArray[i].top+cuurent_OffsetY;
				mRect.right=headRectArray[i].right+current_OffsetX+moving_OffsetX;
				mRect.bottom=headRectArray[i].bottom+cuurent_OffsetY;
				
				if(mRect.contains(x, y))
				{
					current_longPressIndex=i;
					return ;
				}
			}
			super.onLongPress(e);
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
//			Log.v("CusGestureListener", "onScroll");
			
			int tx=(int) e1.getRawX();
			int ty=(int) (e1.getRawY()-WindowMgr.getInstance().getStatusBarHeight(getContext()));
			
			int tx2=(int) e2.getRawX();
			int ty2=(int) (e2.getRawY()-WindowMgr.getInstance().getStatusBarHeight(getContext()));
			
			if(mDragRect.contains(tx, ty)&&mDragRect.contains(tx2, ty2))
			{
				moving_OffsetX=(int) (e2.getRawX()-e1.getRawX());
			}
			return super.onScroll(e1, e2, distanceX, distanceY);
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
//			Log.d("CDH", "onFling currentPage:"+currentPage+" x1:"+e1.getRawX()+" x2:"+e2.getRawX()+" velocityX:"+velocityX+"velocityY:"+velocityY);
			
			if (Math.abs(velocityX) < 1000) return false;
			
			int direct=0;//往左边为1,右边为-1,保持不变为0
			if(velocityX > 0)
			{
				current_Page--;
				direct=1;
				if(current_Page<1) 
				{
					current_Page=1;
					direct=0;
				}
			} 
			else 
			{
				current_Page++;
				direct=-1;
				if(current_Page>maxPage) 
				{
					current_Page=maxPage;
					direct=0;
				}
			}

			int old=current_OffsetX+moving_OffsetX;
			int newX=current_OffsetX+direct*getMeasuredWidth();
			mHandler.post(new SmoothRunnable(old, newX));
			
			return true;
		}

		@Override
		public void onShowPress(MotionEvent e) {
//			Log.v("CusGestureListener", "onShowPress");
			
			super.onShowPress(e);
		}

		@Override
		public boolean onDown(MotionEvent e) {
//			Log.v("CusGestureListener", "onDown");
			
			moving_OffsetX=0;
			mDragRect.set(getLeft(), getTop(), getRight(), getBottom());
			return super.onDown(e);
		}

		@Override
		public boolean onDoubleTap(MotionEvent e) {
//			Log.v("CusGestureListener", "onDoubleTap");
			return super.onDoubleTap(e);
		}

		@Override
		public boolean onDoubleTapEvent(MotionEvent e) {
//			Log.v("CusGestureListener", "onDoubleTapEvent");
			
			return super.onDoubleTapEvent(e);
		}

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e) {
//			Log.v("CusGestureListener", "onSingleTapConfirmed");
			
			return super.onSingleTapConfirmed(e);
		}
	}
	
	//平滑移动
	private class SmoothRunnable implements Runnable
	{
		private int startDx;
		private int endDx;
		private long duration=250;
		private long interval=10;
		private long startTime;
		private long endTime;
		
		public SmoothRunnable(int startDx, int endDx) {
			super();
			this.startDx = startDx;
			this.endDx = endDx;
		}

		@Override
		public void run() {
			if(startTime==0)
			{
				moving_OffsetX=0;
				startTime=System.currentTimeMillis();
				endTime=startTime+duration;
			}
			
			long currentTime=System.currentTimeMillis();
			if(currentTime<endTime)
			{
				current_OffsetX=(int)(startDx+(endDx-startDx)*((float)(currentTime-startTime)/(float)(duration)));
				invalidate();
				mHandler.postDelayed(this, interval);
			}
			else
			{
				current_OffsetX=endDx;
				moving_OffsetX=0;
				invalidate();
			}
		}
	} 

	private int clickOffsetforwarDx=0;
	private int clickOffsetBackDx;
	//点击移动到队头部,其他往后面移动
	private ClickRunnable mClickRunnable=null;
	private class ClickRunnable implements Runnable
	{
		private int current_clickIndex;
		private int forwardDx=0;//到队列头部的总长度
		private int backwardsDx=0;//到第二个的长度
		private long duration=300;
		private long interval=10;
		private long startTime;
		private long endTime;
		
		public ClickRunnable(int current_clickIndex)
		{
			this.current_clickIndex=current_clickIndex;
			duration=Math.max((long) (duration*(float)current_clickIndex/(float)portraitNumberPerScreen),duration);
			duration=Math.min(duration, 700);
		}
		
		public void stop()
		{
			mHandler.removeCallbacks(this);
		}

		@Override
		public void run() {
			if(startTime==0)
			{
				Rect mRect=new Rect();
				mRect.left=headRectArray[current_clickIndex].left+current_OffsetX+moving_OffsetX;
				mRect.top=headRectArray[current_clickIndex].top+cuurent_OffsetY;
				mRect.right=headRectArray[current_clickIndex].right+current_OffsetX+moving_OffsetX;
				mRect.bottom=headRectArray[current_clickIndex].bottom+cuurent_OffsetY;
				
				forwardDx=mRect.left-headRectArray[0].left;
				
				int left=paddingLeft*2+headWidth;
				int top=DensityUtil.dip2px(getContext(), 10);
				Rect secord=new Rect(left,top, left+headWidth, top+headWidth);
				backwardsDx=secord.left-headRectArray[0].left+Math.abs(current_OffsetX);
				
				startTime=System.currentTimeMillis();
				endTime=startTime+duration;
			}
			
			long currentTime=System.currentTimeMillis();
			if(currentTime<endTime)
			{
				clickOffsetforwarDx=(int) (forwardDx-forwardDx*((float)(currentTime-startTime)/(float)(duration)));
				clickOffsetBackDx=(int) (backwardsDx*((float)(currentTime-startTime)/(float)(duration)));
				invalidate();
				mHandler.postDelayed(this, interval);
			}
			else
			{
				clickOffsetforwarDx=0;
				clickOffsetBackDx=0;
				
				String tmp=headList.remove(current_clickIndex);
				headList.add(0, tmp);
				bitmapArray=WindowMgr.getInstance().getAllBitmaps(headList);
				
				HorImageListView.this.current_clickIndex=0;
				HorImageListView.this.current_Page=1;
				HorImageListView.this.current_OffsetX=0;
				HorImageListView.this.current_clickIndex=0;
				HorImageListView.this.current_foucsIndex=0;
				HorImageListView.this.current_longPressIndex=-1;
				invalidate();
				
				if(null!=headClickListener)
				headClickListener.onItemClick(current_clickIndex);
			}
		}
	}
	
	public void setOnHeadClickListener(IHeadClick headClickListener)
	{
		this.headClickListener=headClickListener;
	}
	
	public static interface IHeadClick
	{
		public void onItemClick(int position);
	}
}


Demo下载地址:

http://download.csdn.net/detail/zz7zz7zz/6340747



抱歉!评论已关闭.