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