首页 > 代码库 > 自定义ListView,实现Item侧滑删除及侧滑出菜单效果

自定义ListView,实现Item侧滑删除及侧滑出菜单效果

本程序是基于网上开源项目修改而来,具体来源忘了,懒得搜了,如果有不合适的地方,请原作者联系我,我会及时回复和处理的!

该例子程序中主要包含两个ListView,一个是实现侧滑删除,一个是侧滑出菜单,代码中的注释很全,我就不在赘述了,直接贴上核心代码和效果图,程序源码在最后,如果有不太明白的地方可以留言评论,我会及时回复,大牛勿喷啊!

侧滑删除ListView:

package com.example.testslidelistview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Scroller;

/**
 * 侧滑删除Item的ListView,此处是对网上开源的一个Listview的完善,
 * 实现在手指滑动时item的透明度随之改变,并增加回到原位置的动画过程
 * @author zhangshuo
 */
public class SlideListView extends ListView {
	/**
	 * 当前滑动的ListView position
	 */
	private int slidePosition;
	/**
	 * 手指按下X的坐标
	 */
	private int downY;
	/**
	 * 手指按下Y的坐标
	 */
	private int downX;
	/**
	 * 屏幕宽度
	 */
	private int screenWidth;
	/**
	 * ListView的item
	 */
	private View itemView;
	/**
	 * 滑动类
	 */
	private Scroller scroller;
	private static final int SNAP_VELOCITY = 600;
	/**
	 * 速度追踪对象
	 */
	private VelocityTracker velocityTracker;
	/**
	 * 是否响应滑动,默认为不响应
	 */
	private boolean isSlide = false;
	/**
	 * 认为是用户滑动的最小距离
	 */
	private int mTouchSlop;
	/**
	 *  移除item后的回调接口
	 */
	private RemoveListener mRemoveListener;
	/**
	 *  标示是否移除
	 */
	private boolean isRemove = false;
	/**
	 * 用来指示item滑出屏幕的方向,向左或者向右,用一个枚举值来标记
	 */
	private RemoveDirection removeDirection;

	// 滑动删除方向的枚举值
	public enum RemoveDirection {
		RIGHT, LEFT, NONE;
	}


	public SlideListView(Context context) {
		this(context, null);
	}

	public SlideListView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SlideListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		screenWidth = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();
		scroller = new Scroller(context);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}
	
	/**
	 * 设置滑动删除的回调接口
	 * @param removeListener
	 */
	public void setRemoveListener(RemoveListener removeListener) {
		this.mRemoveListener = removeListener;
	}

	/**
	 * 分发事件,主要做的是判断点击的是那个item, 以及通过postDelayed来设置响应左右滑动事件
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			System.out.println("dispatch-->" + "down");
			addVelocityTracker(event);

			// 假如scroller滚动还没有结束,我们直接返回
			if (!scroller.isFinished()) {
				return false;
			}
			downX = (int) event.getX();
			downY = (int) event.getY();

			slidePosition = pointToPosition(downX, downY);

			// 无效的position, 不做任何处理
			if (slidePosition == AdapterView.INVALID_POSITION) {
				return super.dispatchTouchEvent(event);
			}

			// 获取我们点击的item view
			itemView = getChildAt(slidePosition - getFirstVisiblePosition());
			break;
		}
		case MotionEvent.ACTION_MOVE: {
			System.out.println("dispatch-->" + "move");
			if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY
					|| (Math.abs(event.getX() - downX) > mTouchSlop && Math
							.abs(event.getY() - downY) < mTouchSlop)) {
				isSlide = true;
				
			}
			break;
		}
		case MotionEvent.ACTION_UP:
			recycleVelocityTracker();
			break;
		}

		return super.dispatchTouchEvent(event);
	}

	/**
	 * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
	 */
	private void scrollRight() {
		removeDirection = RemoveDirection.RIGHT;
		final int delta = (screenWidth + itemView.getScrollX());
		// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
		scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
				Math.abs(delta));
		postInvalidate(); // 刷新itemView
	}

	/**
	 * 向左滑动,根据上面我们知道向左滑动为正值
	 */
	private void scrollLeft() {
		removeDirection = RemoveDirection.LEFT;
		final int delta = (screenWidth - itemView.getScrollX());
		// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
		scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
				Math.abs(delta));
		postInvalidate(); // 刷新itemView
	}
	
	/**
	 *  滑动会原来的位置
	 */
	private void scrollBack(){
		removeDirection = RemoveDirection.NONE;
		scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(), 0,
				Math.abs(itemView.getScrollX()));
		postInvalidate(); // 刷新itemView
	}

	/**
	 * 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
	 */
	private void scrollByDistanceX() {
		// 如果向左滚动的距离大于屏幕的二分之一,就让其删除
		if (itemView.getScrollX() >= screenWidth / 2) {
			scrollLeft();
		} else if (itemView.getScrollX() <= -screenWidth / 2) {
			scrollRight();
		} else {
			// 滚回到原始位置
			scrollBack();
		}

	}

	/**
	 * 处理我们拖动ListView item的逻辑
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (isSlide && slidePosition != AdapterView.INVALID_POSITION) {
			System.out.println("touch-->" + "开始");
			requestDisallowInterceptTouchEvent(true);
			addVelocityTracker(ev);
			final int action = ev.getAction();
			int x = (int) ev.getX();
			switch (action) {
			case MotionEvent.ACTION_DOWN:
				System.out.println("touch-->" + "down");
				break;
			case MotionEvent.ACTION_MOVE:
				System.out.println("touch-->" + "move");
				MotionEvent cancelEvent = MotionEvent.obtain(ev);
	            cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
	                       (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
	            onTouchEvent(cancelEvent);
	            
				int deltaX = downX - x;

				// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
				itemView.scrollTo(deltaX, 0);
				// 根据手指滑动的距离,调整透明度
				itemView.setAlpha(1f - Math.abs((float)deltaX/screenWidth));
				
				return true;  //拖动的时候ListView不滚动
			case MotionEvent.ACTION_UP:
				System.out.println("touch-->" + "up");
				// 手指离开的时候就不响应左右滚动
				isSlide = false;
				int velocityX = getScrollVelocity();
				if (velocityX > SNAP_VELOCITY) {
					scrollRight();
				} else if (velocityX < -SNAP_VELOCITY) {
					scrollLeft();
				} else {
					scrollByDistanceX();
				}
				
				recycleVelocityTracker();
				
				break;
			}
		}

		//否则直接交给ListView来处理onTouchEvent事件
		return super.onTouchEvent(ev);
	}

	@Override
	public void computeScroll() {
		// 调用startScroll的时候scroller.computeScrollOffset()返回true,
		if (scroller.computeScrollOffset()) {
			// 让ListView item根据当前的滚动偏移量进行滚动
			itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
			
			itemView.setAlpha(1f - Math.abs((float)scroller.getCurrX()/screenWidth));
			
			postInvalidate();
			
			// 滚动动画结束的时候调用回调接口
			if (scroller.isFinished() && removeDirection != RemoveDirection.NONE) {
				if (mRemoveListener == null) {
					throw new NullPointerException("RemoveListener is null, we should called setRemoveListener()");
				}
				itemView.scrollTo(0, 0);
				itemView.setAlpha(1f);
				mRemoveListener.removeItem(removeDirection, slidePosition);
			}
		}
	}

	/**
	 * 添加用户的速度跟踪器
	 * 
	 * @param event
	 */
	private void addVelocityTracker(MotionEvent event) {
		if (velocityTracker == null) {
			velocityTracker = VelocityTracker.obtain();
		}

		velocityTracker.addMovement(event);
	}

	/**
	 * 移除用户速度跟踪器
	 */
	private void recycleVelocityTracker() {
		if (velocityTracker != null) {
			velocityTracker.recycle();
			velocityTracker = null;
		}
	}

	/**
	 * 获取X方向的滑动速度,大于0向右滑动,反之向左
	 * 
	 * @return
	 */
	private int getScrollVelocity() {
		velocityTracker.computeCurrentVelocity(1000);
		int velocity = (int) velocityTracker.getXVelocity();
		return velocity;
	}

	/**
	 * 
	 * 当ListView item滑出屏幕,回调这个接口
	 * 我们需要在回调方法removeItem()中移除该Item,然后刷新ListView
	 * 
	 * @author xiaanming
	 *
	 */
	public interface RemoveListener {
		public void removeItem(RemoveDirection direction, int position);
	}

}
侧滑菜单ListView:

package com.example.testslidelistview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Scroller;

/**
 * 侧向滑出菜单的ListView
 * 使用请注意与ListView的Item的布局配合,
 * 该效果的实现是基于在Item的布局中通过设置PaddingLeft和PaddingRight来隐藏左右菜单的,
 * 所以使用此ListView时,请务必在布局Item时使用PaddingLeft和PaddingRight;
 * 或者自己改写此ListView,已达到想要的实现方式
 * @author zhangshuo
 */
public class SlideListView2 extends ListView {
	
	/**禁止侧滑模式*/
	public static int MOD_FORBID = 0;
	/**从左向右滑出菜单模式*/
	public static int MOD_LEFT = 1;
	/**从右向左滑出菜单模式*/
	public static int MOD_RIGHT = 2;
	/**左右均可以滑出菜单模式*/
	public static int MOD_BOTH = 3;
	/**当前的模式*/
	private int mode = MOD_FORBID;
	/**左侧菜单的长度*/
	private int leftLength = 0;
	/**右侧菜单的长度*/
	private int rightLength = 0;
	
	/**
	 * 当前滑动的ListView position
	 */
	private int slidePosition;
	/**
	 * 手指按下X的坐标
	 */
	private int downY;
	/**
	 * 手指按下Y的坐标
	 */
	private int downX;
	/**
	 * ListView的item
	 */
	private View itemView;
	/**
	 * 滑动类
	 */
	private Scroller scroller;
	/**
	 * 认为是用户滑动的最小距离
	 */
	private int mTouchSlop;

	/**
	 * 判断是否可以侧向滑动
	 */
	private boolean canMove = false;
	/**
	 * 标示是否完成侧滑
	 */
	private boolean isSlided = false;
	
	public SlideListView2(Context context) {
		this(context, null);
	}

	public SlideListView2(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SlideListView2(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		scroller = new Scroller(context);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	/**
	 * 初始化菜单的滑出模式
	 * @param mode
	 */
	public void initSlideMode(int mode){
		this.mode = mode;
	}
	
	/**
	 * 处理我们拖动ListView item的逻辑
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {

		final int action = ev.getAction();
		int lastX = (int) ev.getX();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("touch-->" + "down");
			
			/*当前模式不允许滑动,则直接返回,交给ListView自身去处理*/
			if(this.mode == MOD_FORBID){
				return super.onTouchEvent(ev);
			}
			
			// 如果处于侧滑完成状态,侧滑回去,并直接返回
			if (isSlided) {
				scrollBack();
				return false;
			}
			// 假如scroller滚动还没有结束,我们直接返回
			if (!scroller.isFinished()) {
				return false;
			}
			downX = (int) ev.getX();
			downY = (int) ev.getY();

			slidePosition = pointToPosition(downX, downY);

			// 无效的position, 不做任何处理
			if (slidePosition == AdapterView.INVALID_POSITION) {
				return super.onTouchEvent(ev);
			}

			// 获取我们点击的item view
			itemView = getChildAt(slidePosition - getFirstVisiblePosition());
			
			/*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/
			if(this.mode == MOD_BOTH){
				this.leftLength = -itemView.getPaddingLeft();
				this.rightLength = -itemView.getPaddingRight();
			}else if(this.mode == MOD_LEFT){
				this.leftLength = -itemView.getPaddingLeft();
			}else if(this.mode == MOD_RIGHT){
				this.rightLength = -itemView.getPaddingRight();
			}
			
			break;
		case MotionEvent.ACTION_MOVE:
			System.out.println("touch-->" + "move");
			
			if (!canMove
					&& slidePosition != AdapterView.INVALID_POSITION
					&& (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
							.getY() - downY) < mTouchSlop)) {
				int offsetX = downX - lastX;
				if(offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*从右向左滑*/
					canMove = true;
				}else if(offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*从左向右滑*/
					canMove = true;
				}else{
					canMove = false;
				}
				/*此段代码是为了避免我们在侧向滑动时同时出发ListView的OnItemClickListener时间*/
				MotionEvent cancelEvent = MotionEvent.obtain(ev);
				cancelEvent
						.setAction(MotionEvent.ACTION_CANCEL
								| (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
				onTouchEvent(cancelEvent);
			}
			if (canMove) {
				/*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/
				requestDisallowInterceptTouchEvent(true);
				
				// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
				int deltaX = downX - lastX;
				if(deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*向左滑*/
					itemView.scrollTo(deltaX, 0);
				}else if(deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*向右滑*/
					itemView.scrollTo(deltaX, 0);
				}else{
					itemView.scrollTo(0, 0);
				}
				return true; // 拖动的时候ListView不滚动
			}
		case MotionEvent.ACTION_UP:
			System.out.println("touch-->" + "up");
			if (canMove){
				canMove = false;
				scrollByDistanceX();
			}
			break;
		}

		// 否则直接交给ListView来处理onTouchEvent事件
		return super.onTouchEvent(ev);
	}

	/**
	 * 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
	 */
	private void scrollByDistanceX() {
		/*当前模式不允许滑动,则直接返回*/
		if(this.mode == MOD_FORBID){
			return;
		}
		if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
			/*从右向左滑*/
			if (itemView.getScrollX() >= rightLength / 2) {
				scrollLeft();
			}  else {
				// 滚回到原始位置
				scrollBack();
			}
		}else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
			/*从左向右滑*/
			if (itemView.getScrollX() <= -leftLength / 2) {
				scrollRight();
			} else {
				// 滚回到原始位置
				scrollBack();
			}
		}else{
			// 滚回到原始位置
			scrollBack();
		}

	}

	/**
	 * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
	 */
	private void scrollRight() {
		isSlided = true;
		final int delta = (leftLength + itemView.getScrollX());
		// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
		scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
				Math.abs(delta));
		postInvalidate(); // 刷新itemView
	}

	/**
	 * 向左滑动,根据上面我们知道向左滑动为正值
	 */
	private void scrollLeft() {
		isSlided = true;
		final int delta = (rightLength - itemView.getScrollX());
		// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
		scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
				Math.abs(delta));
		postInvalidate(); // 刷新itemView
	}

	/**
	 * 滑动会原来的位置
	 */
	private void scrollBack() {
		isSlided = false;
		scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),
				0, Math.abs(itemView.getScrollX()));
		postInvalidate(); // 刷新itemView
	}

	@Override
	public void computeScroll() {
		// 调用startScroll的时候scroller.computeScrollOffset()返回true,
		if (scroller.computeScrollOffset()) {
			// 让ListView item根据当前的滚动偏移量进行滚动
			itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());

			postInvalidate();
		}
	}

	/**
	 * 提供给外部调用,用以将侧滑出来的滑回去
	 */
	public void slideBack() {
		this.scrollBack();
	}

}

注意侧滑菜单ListView的使用需要配合Item布局(主要是PaddingLeft和PaddingRight这两个属性),Item布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="-181dp"
    android:paddingRight="-180dp"
    android:background="@color/wheat"
    android:orientation="horizontal" >

    <LinearLayout
        android:id="@+id/llayout_left"
        android:layout_width="180dp"
        android:layout_height="match_parent" >

        <RelativeLayout
            android:id="@+id/delete1"
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:background="@color/slategray"
            android:clickable="true" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="左删除"
                android:textColor="@color/floralwhite"
                android:textSize="15sp" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/other1"
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:background="@color/tomato"
            android:clickable="true" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="左其他"
                android:textColor="@color/floralwhite"
                android:textSize="15sp" />
        </RelativeLayout>
    </LinearLayout>

    
    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toLeftOf="@+id/llayout_right"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:text="标题"
            android:textColor="@color/orange"
            android:textSize="17sp" />

        <TextView
            android:id="@+id/time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="时间"
            android:textColor="@color/black"
            android:textSize="13sp" />

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="内容"
            android:textColor="@color/black"
            android:textSize="13sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/llayout_right"
        android:layout_width="180dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true" >

        <RelativeLayout
            android:id="@+id/other2"
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:background="@color/slategray"
            android:clickable="true" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="右其他"
                android:textColor="@color/floralwhite"
                android:textSize="15sp" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/delete2"
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:background="@color/tomato"
            android:clickable="true" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="右删除"
                android:textColor="@color/floralwhite"
                android:textSize="15sp" />
        </RelativeLayout>
    </LinearLayout>
    </RelativeLayout>
    

</LinearLayout>
截图:


好了,这里是源码下载地址:http://download.csdn.net/detail/super_spy/7667793


自定义ListView,实现Item侧滑删除及侧滑出菜单效果