listview左右滑动动画实现

关于listview的左右滑动实现,在网上其实已经有很多示例代码了,多数都是将listview嵌套在horiscrollview或是viewpager里,而这两种实现方式都是基于一个父容器里有多个子控件横向排列,在移动过程中通过手势最终实现视图的切换。但有些应用是不需要多个子控件对象,换言之出于节省内存的考究,比如十个item页,每页视图其实都是一个listview,而如果放上10个listview实属浪费。所以将一个listview放置在自定义的容器内,那么在处理手势的时候在判定可左右移动时做个动画,待动画结束后将另一个item页的数据显示出来即可。

     下面先上SlideViewGroup的代码:

   

public class SlideViewGroup extends RelativeLayout implements AnimationListener{

	public static interface onItemChangeListener{
		public void onItemChange(int curItem);
	}

	private final static int OFFSET_X_DISTANCE = 50;
    private float mLastMotionX = 0;     
	private boolean mIsHookTouchEvent = false;
	private boolean mAnimationStart = false;
	private boolean mMoveFinish = true;
	private boolean mMoveLeft = true;
	
	private int mItemCount = 1;
	private int mCurItemIndex = 0;
	private onItemChangeListener mItemChangeListener;
	
	private TranslateAnimation mMoveLeftAnimation;
	private TranslateAnimation mMoveRightAnimation;
	private int mScreenWidth = 0;
	
	
	public SlideViewGroup(Context context) {
		super(context);

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

		init(context);
	}

	public void setOnItemChangeListener(onItemChangeListener listener){
		mItemChangeListener = listener;
	}
	
	public void setItemCount(int itemCount){
		mItemCount = itemCount;
	}
	
	public int getItemCount(){
		return mItemCount;
	}
	
	public void setCurItem(int index){
		if (index < 0){
			index = 0;
		}else if (index >= mItemCount){
			index = mItemCount - 1;
		}
		mCurItemIndex = index;
		clearAnimation();
		mAnimationStart = false;
	}
	
	public int getCurItem(){
		return mCurItemIndex;
	}

	private void init(Context context)
	{  

		reset();
		
		mScreenWidth = getScreenWidth(context);

		mMoveLeftAnimation = new TranslateAnimation(0.0f, -mScreenWidth,0.0f,0.0f);  
		mMoveLeftAnimation.setDuration(500);
    	
		mMoveRightAnimation = new TranslateAnimation(0.0f, mScreenWidth,0.0f,0.0f);  
		mMoveRightAnimation.setDuration(500);
	
		mMoveLeftAnimation.setAnimationListener(this);
		mMoveRightAnimation.setAnimationListener(this);
	}
	
	private void reset(){
	   mLastMotionX = 0;    
	   mIsHookTouchEvent = false;
	   mAnimationStart = false;
	   mMoveFinish = true;
	   mMoveLeft = true;
	   mItemCount = 1;
	   mCurItemIndex = 0;
	}

	
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		
		int action = ev.getAction();
		
		final float x = ev.getX();
        final float y = ev.getY();
        switch (action) {
        case MotionEvent.ACTION_MOVE:
        		if (mIsHookTouchEvent){
        			return true;
        		}
        	  	final int xDiff = (int) Math.abs(x - mLastMotionX);
                if (xDiff > OFFSET_X_DISTANCE) {
                	mIsHookTouchEvent = true;
                }           
                break;

        case MotionEvent.ACTION_DOWN:
                mLastMotionX = x;
                mIsHookTouchEvent = mAnimationStart;
                break;

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
        		mIsHookTouchEvent = false;
                break;
               default:
            	   break;
        }
		
    
		return mIsHookTouchEvent;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {

	    if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
            return false;
        }
		
	    int action = event.getAction();
		
		final float x = event.getX();
        final float y = event.getY();
        switch (action) {
        case MotionEvent.ACTION_MOVE:
        	 	if (mIsHookTouchEvent && !mMoveFinish){	       
        	 		if (!mAnimationStart){
        	 			boolean directionLeft = x < mLastMotionX ? true : false;
        	 			if (isCanMoveDirection(directionLeft)){
        	 				startMoveAnimotion(directionLeft);
        	 			}
        	 		} 	  
        	 		mMoveFinish = true;
        	 	}
                break;

        case MotionEvent.ACTION_DOWN:
                mLastMotionX = x;
                break;

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
    			mMoveFinish = false;
                break;
               default:
            	   break;
        }
		
    
		return true;
		
	}
	

	private void startMoveAnimotion(boolean isLeft){
		if (isLeft){
			startAnimation(mMoveLeftAnimation);
		}else{
			startAnimation(mMoveRightAnimation);
		}
		
		mMoveLeft = isLeft;
	}
	
	private boolean isCanMoveDirection(boolean moveLeft){
		
		if (moveLeft){
			if (mCurItemIndex < mItemCount - 1){
				return true;
			}
		}else{
			if (mCurItemIndex > 0){
				return true;
			}
		}		
		
		return false;
	}
	
	private void changeItemAuto(boolean moveLeft){

		if (moveLeft){
			mCurItemIndex++;
			if (mCurItemIndex >= mItemCount){
				mCurItemIndex = mItemCount - 1;
			}
		}else{
			mCurItemIndex--;
			if (mCurItemIndex < 0){
				mCurItemIndex = 0;
			}
		}
		if (mItemChangeListener != null){
			mItemChangeListener.onItemChange(mCurItemIndex);
		}
	}
	
	
	private  int getScreenWidth(Context context) {
		WindowManager manager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
		Display display = manager.getDefaultDisplay();
		return display.getWidth();
	}

	@Override
	public void onAnimationStart(Animation animation) {
		mAnimationStart = true;
	}

	@Override
	public void onAnimationEnd(Animation animation) {
		mAnimationStart = false;
		changeItemAuto(mMoveLeft);
	}

	@Override
	public void onAnimationRepeat(Animation animation) {
		
	}
}

考虑到包含着的是listview子控件时touch事件会被listview优先处理掉,所以需要重写onInterceptTouchEvent,判断  final int xDiff = (int) Math.abs(x - mLastMotionX);

大于某值时认为左右滑动的条件成立,返回false,即将touch事件钩住,由容器自身直接处理不传递给子控件(对android的touch事件传递机制还不熟的同学先百度下补补课)

之后onTouchEvent的处理就是做动画那些事了,里面那些mAnimationStart和mMoveFinish这些变量都是为了在做动画的时候屏蔽外界的触屏事件,避免出现混乱。

看到这里,有些同学可能会疑惑,既然容器的onInterceptTouchEvent里在左右移动到达满足条件时就会将touch事件勾走,那listview在上下可滑动发生后就应该不能再触发容器左右移动的事件,这又当如何处理?其实通过剖析listview的源码,准确来说应是AbsListView的onTouchEvent事件的实现,在move的事件处理有有个方法startScrollIfNeeded(int deltaY)

截取部分代码:

final int distance = Math.abs(deltaY);
        if (distance > mTouchSlop){

            ........

              ........

        requestDisallowInterceptTouchEvent(true);
            return true;
           }
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

mParent.requestDisallowInterceptTouchEvent(disallowIntercept);即使其父容器不再处理onInterceptTouchEvent事件而防止touch事件被勾走。

由此我们还可以发现,listview在上下滑动时只有值超过mTouchSlop时才认为可移动,经测试,此值为24.之所以谈到这个,是因为若我们的子控件使用下拉刷新的那种listview的话,若不做额外处理,在下拉未超出24像素情况下再左右滑动是会触发容器事件的,而这样的视觉效果是不对的,具体详看listview里的代码处理。

最后再截两张图:

附上代码工程:http://download.csdn.net/detail/geniuseoe2012/4958745

更多精彩,请留意窝的博客更新。。。

原文地址:https://www.cnblogs.com/lance2016/p/5204241.html