关于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
更多精彩,请留意窝的博客更新。。。