Android 自定义ListView Item侧滑删除

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

该例子程序中主要包含两个ListView,一个是实现侧滑删除,一个是侧滑出菜单,代码中的注释很全,我就不在赘述了,直接贴上核心代码和效果图。

侧滑删除ListView:

 


002.package com.example.testslidelistview;
003.import android.content.Context;
004.import android.util.AttributeSet;
005.import android.view.MotionEvent;
006.import android.view.VelocityTracker;
007.import android.view.View;
008.import android.view.ViewConfiguration;
009.import android.view.WindowManager;
010.import android.widget.AdapterView;
011.import android.widget.ListView;
012.import android.widget.Scroller;
013. 
014./**
015.* 侧滑删除Item的ListView,此处是对网上开源的一个Listview的完善,
016.* 实现在手指滑动时item的透明度随之改变,并增加回到原位置的动画过程
017.* @author zhangshuo
018.*/
019.public class SlideListView extends ListView {
020./**
021.* 当前滑动的ListView position
022.*/
023.private int slidePosition;
024./**
025.* 手指按下X的坐标
026.*/
027.private int downY;
028./**
029.* 手指按下Y的坐标
030.*/
031.private int downX;
032./**
033.* 屏幕宽度
034.*/
035.private int screenWidth;
036./**
037.* ListView的item
038.*/
039.private View itemView;
040./**
041.* 滑动类
042.*/
043.private Scroller scroller;
044.private static final int SNAP_VELOCITY = 600;
045./**
046.* 速度追踪对象
047.*/
048.private VelocityTracker velocityTracker;
049./**
050.* 是否响应滑动,默认为不响应
051.*/
052.private boolean isSlide = false;
053./**
054.* 认为是用户滑动的最小距离
055.*/
056.private int mTouchSlop;
057./**
058.*  移除item后的回调接口
059.*/
060.private RemoveListener mRemoveListener;
061./**
062.*  标示是否移除
063.*/
064.private boolean isRemove = false;
065./**
066.* 用来指示item滑出屏幕的方向,向左或者向右,用一个枚举值来标记
067.*/
068.private RemoveDirection removeDirection;
069. 
070.// 滑动删除方向的枚举值
071.public enum RemoveDirection {
072.RIGHT, LEFT, NONE;
073.}
074. 
075. 
076.public SlideListView(Context context) {
077.this(context, null);
078.}
079. 
080.public SlideListView(Context context, AttributeSet attrs) {
081.this(context, attrs, 0);
082.}
083. 
084.public SlideListView(Context context, AttributeSet attrs, int defStyle) {
085.super(context, attrs, defStyle);
086.screenWidth = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();
087.scroller = new Scroller(context);
088.mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
089.}
090. 
091./**
092.* 设置滑动删除的回调接口
093.* @param removeListener
094.*/
095.public void setRemoveListener(RemoveListener removeListener) {
096.this.mRemoveListener = removeListener;
097.}
098. 
099./**
100.* 分发事件,主要做的是判断点击的是那个item, 以及通过postDelayed来设置响应左右滑动事件
101.*/
102.@Override
103.public boolean dispatchTouchEvent(MotionEvent event) {
104.switch (event.getAction()) {
105.case MotionEvent.ACTION_DOWN: {
106.System.out.println("dispatch-->" "down");
107.addVelocityTracker(event);
108. 
109.// 假如scroller滚动还没有结束,我们直接返回
110.if (!scroller.isFinished()) {
111.return false;
112.}
113.downX = (int) event.getX();
114.downY = (int) event.getY();
115. 
116.slidePosition = pointToPosition(downX, downY);
117. 
118.// 无效的position, 不做任何处理
119.if (slidePosition == AdapterView.INVALID_POSITION) {
120.return super.dispatchTouchEvent(event);
121.}
122. 
123.// 获取我们点击的item view
124.itemView = getChildAt(slidePosition - getFirstVisiblePosition());
125.break;
126.}
127.case MotionEvent.ACTION_MOVE: {
128.System.out.println("dispatch-->" "move");
129.if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY
130.|| (Math.abs(event.getX() - downX) > mTouchSlop && Math
131..abs(event.getY() - downY) < mTouchSlop)) {
132.isSlide = true;
133. 
134.}
135.break;
136.}
137.case MotionEvent.ACTION_UP:
138.recycleVelocityTracker();
139.break;
140.}
141. 
142.return super.dispatchTouchEvent(event);
143.}
144. 
145./**
146.* 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
147.*/
148.private void scrollRight() {
149.removeDirection = RemoveDirection.RIGHT;
150.final int delta = (screenWidth + itemView.getScrollX());
151.// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
152.scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
153.Math.abs(delta));
154.postInvalidate(); // 刷新itemView
155.}
156. 
157./**
158.* 向左滑动,根据上面我们知道向左滑动为正值
159.*/
160.private void scrollLeft() {
161.removeDirection = RemoveDirection.LEFT;
162.final int delta = (screenWidth - itemView.getScrollX());
163.// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
164.scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
165.Math.abs(delta));
166.postInvalidate(); // 刷新itemView
167.}
168. 
169./**
170.*  滑动会原来的位置
171.*/
172.private void scrollBack(){
173.removeDirection = RemoveDirection.NONE;
174.scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(), 0,
175.Math.abs(itemView.getScrollX()));
176.postInvalidate(); // 刷新itemView
177.}
178. 
179./**
180.* 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
181.*/
182.private void scrollByDistanceX() {
183.// 如果向左滚动的距离大于屏幕的二分之一,就让其删除
184.if (itemView.getScrollX() >= screenWidth / 2) {
185.scrollLeft();
186.else if (itemView.getScrollX() <= -screenWidth / 2) {
187.scrollRight();
188.else {
189.// 滚回到原始位置
190.scrollBack();
191.}
192. 
193.}
194. 
195./**
196.* 处理我们拖动ListView item的逻辑
197.*/
198.@Override
199.public boolean onTouchEvent(MotionEvent ev) {
200.if (isSlide && slidePosition != AdapterView.INVALID_POSITION) {
201.System.out.println("touch-->" "开始");
202.requestDisallowInterceptTouchEvent(true);
203.addVelocityTracker(ev);
204.final int action = ev.getAction();
205.int x = (int) ev.getX();
206.switch (action) {
207.case MotionEvent.ACTION_DOWN:
208.System.out.println("touch-->" "down");
209.break;
210.case MotionEvent.ACTION_MOVE:
211.System.out.println("touch-->" "move");
212.MotionEvent cancelEvent = MotionEvent.obtain(ev);
213.cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
214.(ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
215.onTouchEvent(cancelEvent);
216. 
217.int deltaX = downX - x;
218. 
219.// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
220.itemView.scrollTo(deltaX, 0);
221.// 根据手指滑动的距离,调整透明度
222.itemView.setAlpha(1f - Math.abs((float)deltaX/screenWidth));
223. 
224.return true;  //拖动的时候ListView不滚动
225.case MotionEvent.ACTION_UP:
226.System.out.println("touch-->" "up");
227.// 手指离开的时候就不响应左右滚动
228.isSlide = false;
229.int velocityX = getScrollVelocity();
230.if (velocityX > SNAP_VELOCITY) {
231.scrollRight();
232.else if (velocityX < -SNAP_VELOCITY) {
233.scrollLeft();
234.else {
235.scrollByDistanceX();
236.}
237. 
238.recycleVelocityTracker();
239. 
240.break;
241.}
242.}
243. 
244.//否则直接交给ListView来处理onTouchEvent事件
245.return super.onTouchEvent(ev);
246.}
247. 
248.@Override
249.public void computeScroll() {
250.// 调用startScroll的时候scroller.computeScrollOffset()返回true,
251.if (scroller.computeScrollOffset()) {
252.// 让ListView item根据当前的滚动偏移量进行滚动
253.itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
254. 
255.itemView.setAlpha(1f - Math.abs((float)scroller.getCurrX()/screenWidth));
256. 
257.postInvalidate();
258. 
259.// 滚动动画结束的时候调用回调接口
260.if (scroller.isFinished() && removeDirection != RemoveDirection.NONE) {
261.if (mRemoveListener == null) {
262.throw new NullPointerException("RemoveListener is null, we should called setRemoveListener()");
263.}
264.itemView.scrollTo(00);
265.itemView.setAlpha(1f);
266.mRemoveListener.removeItem(removeDirection, slidePosition);
267.}
268.}
269.}
270. 
271./**
272.* 添加用户的速度跟踪器
273.*
274.* @param event
275.*/
276.private void addVelocityTracker(MotionEvent event) {
277.if (velocityTracker == null) {
278.velocityTracker = VelocityTracker.obtain();
279.}
280. 
281.velocityTracker.addMovement(event);
282.}
283. 
284./**
285.* 移除用户速度跟踪器
286.*/
287.private void recycleVelocityTracker() {
288.if (velocityTracker != null) {
289.velocityTracker.recycle();
290.velocityTracker = null;
291.}
292.}
293. 
294./**
295.* 获取X方向的滑动速度,大于0向右滑动,反之向左
296.*
297.* @return
298.*/
299.private int getScrollVelocity() {
300.velocityTracker.computeCurrentVelocity(1000);
301.int velocity = (int) velocityTracker.getXVelocity();
302.return velocity;
303.}
304. 
305./**
306.*
307.* 当ListView item滑出屏幕,回调这个接口
308.* 我们需要在回调方法removeItem()中移除该Item,然后刷新ListView
309.*
310.* @author xiaanming
311.*
312.*/
313.public interface RemoveListener {
314.public void removeItem(RemoveDirection direction, int position);
315.}
316. 
317.}
侧滑菜单ListView:

 

 

001.package com.example.testslidelistview;
002. 
003.import android.content.Context;
004.import android.util.AttributeSet;
005.import android.view.MotionEvent;
006.import android.view.View;
007.import android.view.ViewConfiguration;
008.import android.widget.AdapterView;
009.import android.widget.ListView;
010.import android.widget.Scroller;
011. 
012./**
013.* 侧向滑出菜单的ListView
014.* 使用请注意与ListView的Item的布局配合,
015.* 该效果的实现是基于在Item的布局中通过设置PaddingLeft和PaddingRight来隐藏左右菜单的,
016.* 所以使用此ListView时,请务必在布局Item时使用PaddingLeft和PaddingRight;
017.* 或者自己改写此ListView,已达到想要的实现方式
018.* @author zhangshuo
019.*/
020.public class SlideListView2 extends ListView {
021. 
022./**禁止侧滑模式*/
023.public static int MOD_FORBID = 0;
024./**从左向右滑出菜单模式*/
025.public static int MOD_LEFT = 1;
026./**从右向左滑出菜单模式*/
027.public static int MOD_RIGHT = 2;
028./**左右均可以滑出菜单模式*/
029.public static int MOD_BOTH = 3;
030./**当前的模式*/
031.private int mode = MOD_FORBID;
032./**左侧菜单的长度*/
033.private int leftLength = 0;
034./**右侧菜单的长度*/
035.private int rightLength = 0;
036. 
037./**
038.* 当前滑动的ListView position
039.*/
040.private int slidePosition;
041./**
042.* 手指按下X的坐标
043.*/
044.private int downY;
045./**
046.* 手指按下Y的坐标
047.*/
048.private int downX;
049./**
050.* ListView的item
051.*/
052.private View itemView;
053./**
054.* 滑动类
055.*/
056.private Scroller scroller;
057./**
058.* 认为是用户滑动的最小距离
059.*/
060.private int mTouchSlop;
061. 
062./**
063.* 判断是否可以侧向滑动
064.*/
065.private boolean canMove = false;
066./**
067.* 标示是否完成侧滑
068.*/
069.private boolean isSlided = false;
070. 
071.public SlideListView2(Context context) {
072.this(context, null);
073.}
074. 
075.public SlideListView2(Context context, AttributeSet attrs) {
076.this(context, attrs, 0);
077.}
078. 
079.public SlideListView2(Context context, AttributeSet attrs, int defStyle) {
080.super(context, attrs, defStyle);
081.scroller = new Scroller(context);
082.mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
083.}
084. 
085./**
086.* 初始化菜单的滑出模式
087.* @param mode
088.*/
089.public void initSlideMode(int mode){
090.this.mode = mode;
091.}
092. 
093./**
094.* 处理我们拖动ListView item的逻辑
095.*/
096.@Override
097.public boolean onTouchEvent(MotionEvent ev) {
098. 
099.final int action = ev.getAction();
100.int lastX = (int) ev.getX();
101. 
102.switch (action) {
103.case MotionEvent.ACTION_DOWN:
104.System.out.println("touch-->" "down");
105. 
106./*当前模式不允许滑动,则直接返回,交给ListView自身去处理*/
107.if(this.mode == MOD_FORBID){
108.return super.onTouchEvent(ev);
109.}
110. 
111.// 如果处于侧滑完成状态,侧滑回去,并直接返回
112.if (isSlided) {
113.scrollBack();
114.return false;
115.}
116.// 假如scroller滚动还没有结束,我们直接返回
117.if (!scroller.isFinished()) {
118.return false;
119.}
120.downX = (int) ev.getX();
121.downY = (int) ev.getY();
122. 
123.slidePosition = pointToPosition(downX, downY);
124. 
125.// 无效的position, 不做任何处理
126.if (slidePosition == AdapterView.INVALID_POSITION) {
127.return super.onTouchEvent(ev);
128.}
129. 
130.// 获取我们点击的item view
131.itemView = getChildAt(slidePosition - getFirstVisiblePosition());
132. 
133./*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/
134.if(this.mode == MOD_BOTH){
135.this.leftLength = -itemView.getPaddingLeft();
136.this.rightLength = -itemView.getPaddingRight();
137.}else if(this.mode == MOD_LEFT){
138.this.leftLength = -itemView.getPaddingLeft();
139.}else if(this.mode == MOD_RIGHT){
140.this.rightLength = -itemView.getPaddingRight();
141.}
142. 
143.break;
144.case MotionEvent.ACTION_MOVE:
145.System.out.println("touch-->" "move");
146. 
147.if (!canMove
148.&& slidePosition != AdapterView.INVALID_POSITION
149.&& (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
150..getY() - downY) < mTouchSlop)) {
151.int offsetX = downX - lastX;
152.if(offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
153./*从右向左滑*/
154.canMove = true;
155.}else if(offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
156./*从左向右滑*/
157.canMove = true;
158.}else{
159.canMove = false;
160.}
161./*此段代码是为了避免我们在侧向滑动时同时出发ListView的OnItemClickListener时间*/
162.MotionEvent cancelEvent = MotionEvent.obtain(ev);
163.cancelEvent
164..setAction(MotionEvent.ACTION_CANCEL
165.| (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
166.onTouchEvent(cancelEvent);
167.}
168.if (canMove) {
169./*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/
170.requestDisallowInterceptTouchEvent(true);
171. 
172.// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
173.int deltaX = downX - lastX;
174.if(deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
175./*向左滑*/
176.itemView.scrollTo(deltaX, 0);
177.}else if(deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
178./*向右滑*/
179.itemView.scrollTo(deltaX, 0);
180.}else{
181.itemView.scrollTo(00);
182.}
183.return true// 拖动的时候ListView不滚动
184.}
185.case MotionEvent.ACTION_UP:
186.System.out.println("touch-->" "up");
187.if (canMove){
188.canMove = false;
189.scrollByDistanceX();
190.}
191.break;
192.}
193. 
194.// 否则直接交给ListView来处理onTouchEvent事件
195.return super.onTouchEvent(ev);
196.}
197. 
198./**
199.* 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
200.*/
201.private void scrollByDistanceX() {
202./*当前模式不允许滑动,则直接返回*/
203.if(this.mode == MOD_FORBID){
204.return;
205.}
206.if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
207./*从右向左滑*/
208.if (itemView.getScrollX() >= rightLength / 2) {
209.scrollLeft();
210.}  else {
211.// 滚回到原始位置
212.scrollBack();
213.}
214.}else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
215./*从左向右滑*/
216.if (itemView.getScrollX() <= -leftLength / 2) {
217.scrollRight();
218.else {
219.// 滚回到原始位置
220.scrollBack();
221.}
222.}else{
223.// 滚回到原始位置
224.scrollBack();
225.}
226. 
227.}
228. 
229./**
230.* 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
231.*/
232.private void scrollRight() {
233.isSlided = true;
234.final int delta = (leftLength + itemView.getScrollX());
235.// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
236.scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
237.Math.abs(delta));
238.postInvalidate(); // 刷新itemView
239.}
240. 
241./**
242.* 向左滑动,根据上面我们知道向左滑动为正值
243.*/
244.private void scrollLeft() {
245.isSlided = true;
246.final int delta = (rightLength - itemView.getScrollX());
247.// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
248.scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
249.Math.abs(delta));
250.postInvalidate(); // 刷新itemView
251.}
252. 
253./**
254.* 滑动会原来的位置
255.*/
256.private void scrollBack() {
257.isSlided = false;
258.scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),
259.0, Math.abs(itemView.getScrollX()));
260.postInvalidate(); // 刷新itemView
261.}
262. 
263.@Override
264.public void computeScroll() {
265.// 调用startScroll的时候scroller.computeScrollOffset()返回true,
266.if (scroller.computeScrollOffset()) {
267.// 让ListView item根据当前的滚动偏移量进行滚动
268.itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
269. 
270.postInvalidate();
271.}
272.}
273. 
274./**
275.* 提供给外部调用,用以将侧滑出来的滑回去
276.*/
277.public void slideBack() {
278.this.scrollBack();
279.}
280. 
281.}

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

 

 

001.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
003.android:layout_width="match_parent"
004.android:layout_height="match_parent"
005.android:paddingLeft="-181dp"
006.android:paddingRight="-180dp"
007.android:background="@color/wheat"
008.android:orientation="horizontal" >
009. 
010.<LinearLayout
011.android:id="@+id/llayout_left"
012.android:layout_width="180dp"
013.android:layout_height="match_parent" >
014. 
015.<RelativeLayout
016.android:id="@+id/delete1"
017.android:layout_width="90dp"
018.android:layout_height="match_parent"
019.android:background="@color/slategray"
020.android:clickable="true" >
021. 
022.<TextView
023.android:layout_width="wrap_content"
024.android:layout_height="wrap_content"
025.android:layout_centerInParent="true"
026.android:gravity="center"
027.android:text="左删除"
028.android:textColor="@color/floralwhite"
029.android:textSize="15sp" />
030.</RelativeLayout>
031. 
032.<RelativeLayout
033.android:id="@+id/other1"
034.android:layout_width="90dp"
035.android:layout_height="match_parent"
036.android:background="@color/tomato"
037.android:clickable="true" >
038. 
039.<TextView
040.android:layout_width="wrap_content"
041.android:layout_height="wrap_content"
042.android:layout_centerInParent="true"
043.android:gravity="center"
044.android:text="左其他"
045.android:textColor="@color/floralwhite"
046.android:textSize="15sp" />
047.</RelativeLayout>
048.</LinearLayout>
049. 
050. 
051.<RelativeLayout
052.android:layout_width="match_parent"
053.android:layout_height="match_parent">
054.<LinearLayout
055.android:layout_width="match_parent"
056.android:layout_height="match_parent"
057.android:layout_toLeftOf="@+id/llayout_right"
058.android:orientation="vertical" >
059. 
060.<TextView
061.android:id="@+id/title"
062.android:layout_width="match_parent"
063.android:layout_height="wrap_content"
064.android:gravity="center_vertical"
065.android:paddingLeft="10dp"
066.android:paddingRight="10dp"
067.android:text="标题"
068.android:textColor="@color/orange"
069.android:textSize="17sp" />
070. 
071.<TextView
072.android:id="@+id/time"
073.android:layout_width="wrap_content"
074.android:layout_height="wrap_content"
075.android:layout_marginLeft="10dp"
076.android:text="时间"
077.android:textColor="@color/black"
078.android:textSize="13sp" />
079. 
080.<TextView
081.android:id="@+id/content"
082.android:layout_width="wrap_content"
083.android:layout_height="wrap_content"
084.android:layout_marginLeft="10dp"
085.android:text="内容"
086.android:textColor="@color/black"
087.android:textSize="13sp" />
088.</LinearLayout>
089. 
090.<LinearLayout
091.android:id="@+id/llayout_right"
092.android:layout_width="180dp"
093.android:layout_height="match_parent"
094.android:layout_alignParentRight="true" >
095. 
096.<RelativeLayout
097.android:id="@+id/other2"
098.android:layout_width="90dp"
099.android:layout_height="match_parent"
100.android:background="@color/slategray"
101.android:clickable="true" >
102. 
103.<TextView
104.android:layout_width="wrap_content"
105.android:layout_height="wrap_content"
106.android:layout_centerInParent="true"
107.android:gravity="center"
108.android:text="右其他"
109.android:textColor="@color/floralwhite"
110.android:textSize="15sp" />
111.</RelativeLayout>
112. 
113.<RelativeLayout
114.android:id="@+id/delete2"
115.android:layout_width="90dp"
116.android:layout_height="match_parent"
117.android:background="@color/tomato"
118.android:clickable="true" >
119. 
120.<TextView
121.android:layout_width="wrap_content"
122.android:layout_height="wrap_content"
123.android:layout_centerInParent="true"
124.android:gravity="center"
125.android:text="右删除"
126.android:textColor="@color/floralwhite"
127.android:textSize="15sp" />
128.</RelativeLayout>
129.</LinearLayout>
130.</RelativeLayout>
131. 
132. 
133.</LinearLayout>

截图:


posted @ 2014-11-05 17:23  jasonkent27  阅读(495)  评论(0编辑  收藏  举报