今天还是给大家带来自定义控件的编写,自定义一个ListView的左右滑动删除Item的效果,这个效果之前已经实现过了,有兴趣的可以看下​​Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果​​,之前使用的是滑动类Scroller来实现的,但是看了下通知栏的左右滑动删除效果,确实很棒,当我们滑动Item超过一半的时候,item的透明度就变成了0,我们就知道抬起手指的时候item就被删除了,当item的透明度不为0的时候,我们抬起手指Item会回到起始位置,这样我们就知道拖动到什么位置item会删除,什么位置Item不删除,用户体验更好了,还有一个效果,就是我们滑动删除了item的时候,ListView的其他item会出现向上或者向下滚动的效果,感觉效果很棒,所以在GitHub上面搜索了下,发现很多开源库都有这个效果,比如​​ListViewAnimations​​​, ​​android-swipelistview​​​等等,我看了下实现原理,使用的是Jake Wharton的动画开源库​​NineOldAndroids​​,这个库究竟是干嘛的呢?在API3.0(Honeycomb), SDK新增了一个android.animation包,里面的类是实现动画效果相关的类,通过Honeycomb API,能够实现非常复杂的动画效果,但是如果开发者想在3.0以下使用这一套API, 则需要使用开源框架Nine Old Androids,在这个库中会根据我们运行的机器判断其SDK版本,如果是API3.0以上则使用Android自带的动画类,否则就使用Nine Old Androids库中,这是一个兼容库,接下来我们就来看看这个效果的具体实现吧

实现该效果的主要思路

  1. 先根据手指触摸的点来获取点击的是ListView的哪一个Item
  2. 当手指在屏幕上面滑动的时候,我们要使得Item跟随手指的滑动而滑动
  3. 当我们抬起手指的时候,我们根据滑动的距离或者手指在屏幕上面的速度来判断Item是滑出屏幕还是滑动至其实位置
  4. Item滑出屏幕时,使ListView的其他item产生向上挤压或者向下挤压的效果

大致的思路这是这四步,其中的一些细节接下来我会一一为大家解答的,接下来我们就用代码来实现这种效果吧

首先我们新建一个工程,叫Swipedismisslistview,我们需要将Nine Old Androids这个库引入到工程,大家可以去​​https://github.com/JakeWharton/NineOldAndroids​​下载,可以使用Jar包,也可以使用工程库的形式引入到我们自己的工程,我们还需要自定义一个ListView,我们先看代码然后给大家讲解下具体的功能实现


[java] ​​ view plain​​ ​​cop



  1. package
  2.   
  3. import static
  4. import static
  5. import
  6. import
  7. import
  8. import
  9. import
  10. import
  11. import
  12. import
  13. import
  14.   
  15. import
  16. import
  17. import
  18. import
  19. import
  20. /**
  21.  * @blog http://blog.csdn.net/xiaanming
  22.  * 
  23.  * @author xiaanming
  24.  *
  25.  */
  26. public class SwipeDismissListView extends
  27. /**
  28.      * 认为是用户滑动的最小距离
  29.      */
  30. private int
  31. /**
  32.      * 滑动的最小速度
  33.      */
  34. private int
  35. /**
  36.      * 滑动的最大速度
  37.      */
  38. private int
  39. /**
  40.      * 执行动画的时间
  41.      */
  42. protected long mAnimationTime = 150;  
  43. /**
  44.      * 用来标记用户是否正在滑动中
  45.      */
  46. private boolean
  47. /**
  48.      * 滑动速度检测类
  49.      */
  50. private
  51. /**
  52.      * 手指按下的position
  53.      */
  54. private int
  55. /**
  56.      * 按下的item对应的View
  57.      */
  58. private
  59. private float
  60. private float
  61. /**
  62.      * item的宽度
  63.      */
  64. private int
  65. /**
  66.      * 当ListView的Item滑出界面回调的接口
  67.      */
  68. private
  69.   
  70. /**
  71.      * 设置动画时间
  72.      * 
  73.      * @param mAnimationTime
  74.      */
  75. public void setmAnimationTime(long
  76. this.mAnimationTime = mAnimationTime;  
  77.     }  
  78.   
  79. /**
  80.      * 设置删除回调接口
  81.      * 
  82.      * @param onDismissCallback
  83.      */
  84. public void
  85. this.onDismissCallback = onDismissCallback;  
  86.     }  
  87.   
  88. public
  89. this(context, null);  
  90.     }  
  91.   
  92. public
  93. this(context, attrs, 0);  
  94.     }  
  95.   
  96. public
  97. int
  98. super(context, attrs, defStyle);  
  99.   
  100.         ViewConfiguration vc = ViewConfiguration.get(context);  
  101.         mSlop = vc.getScaledTouchSlop();  
  102. 8; //获取滑动的最小速度
  103. //获取滑动的最大速度
  104.     }  
  105.   
  106.       
  107. @Override
  108. public boolean
  109. switch
  110. case
  111.             handleActionDown(ev);  
  112. break;  
  113. case
  114. return
  115. case
  116.             handleActionUp(ev);  
  117. break;  
  118.         }  
  119. return super.onTouchEvent(ev);  
  120.     }  
  121.   
  122. /**
  123.      * 按下事件处理
  124.      * 
  125.      * @param ev
  126.      * @return
  127.      */
  128. private void
  129.         mDownX = ev.getX();  
  130.         mDownY = ev.getY();  
  131.           
  132. int) mDownX, (int) mDownY);  
  133.   
  134. if
  135. return;  
  136.         }  
  137.   
  138.         mDownView = getChildAt(mDownPosition - getFirstVisiblePosition());  
  139.   
  140. if (mDownView != null) {  
  141.             mViewWidth = mDownView.getWidth();  
  142.         }  
  143.   
  144. //加入速度检测
  145.         mVelocityTracker = VelocityTracker.obtain();  
  146.         mVelocityTracker.addMovement(ev);  
  147.     }  
  148.       
  149.   
  150. /**
  151.      * 处理手指滑动的方法
  152.      * 
  153.      * @param ev
  154.      * @return
  155.      */
  156. private boolean
  157. if (mVelocityTracker == null || mDownView == null) {  
  158. return super.onTouchEvent(ev);  
  159.         }  
  160.   
  161. // 获取X方向滑动的距离
  162. float
  163. float
  164.   
  165. // X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动
  166. if
  167. true;  
  168.               
  169. //当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生
  170.             MotionEvent cancelEvent = MotionEvent.obtain(ev);  
  171.             cancelEvent.setAction(MotionEvent.ACTION_CANCEL |  
  172.                        (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
  173.             onTouchEvent(cancelEvent);  
  174.         }  
  175.   
  176. if
  177. // 跟谁手指移动item
  178.             ViewHelper.setTranslationX(mDownView, deltaX);  
  179. // 透明度渐变
  180.             ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX)/ mViewWidth)));  
  181.   
  182. // 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理
  183. return true;  
  184.         }  
  185.   
  186. return super.onTouchEvent(ev);  
  187.   
  188.     }  
  189.   
  190. /**
  191.      * 手指抬起的事件处理
  192.      * @param ev
  193.      */
  194. private void
  195. if (mVelocityTracker == null || mDownView == null|| !mSwiping) {  
  196. return;  
  197.         }  
  198.   
  199. float
  200.           
  201. //通过滑动的距离计算出X,Y方向的速度
  202. 1000);  
  203. float
  204. float
  205.           
  206. boolean dismiss = false; //item是否要滑出屏幕
  207. boolean dismissRight = false;//是否往右边删除
  208.           
  209. //当拖动item的距离大于item的一半,item滑出屏幕
  210. if (Math.abs(deltaX) > mViewWidth / 2) {  
  211. true;  
  212. 0;  
  213.               
  214. //手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕
  215. else if
  216.                 && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {  
  217. true;  
  218. 0;  
  219.         }  
  220.   
  221. if
  222.             ViewPropertyAnimator.animate(mDownView)  
  223. //X轴方向的移动距离
  224. 0)  
  225.                     .setDuration(mAnimationTime)  
  226. new
  227. @Override
  228. public void
  229. //Item滑出界面之后执行删除
  230.                             performDismiss(mDownView, mDownPosition);  
  231.                         }  
  232.                     });  
  233. else
  234. //将item滑动至开始位置
  235.             ViewPropertyAnimator.animate(mDownView)  
  236. 0)  
  237. 1)     
  238. null);  
  239.         }  
  240.           
  241. //移除速度检测
  242. if(mVelocityTracker != null){  
  243.             mVelocityTracker.recycle();  
  244. null;  
  245.         }  
  246.           
  247. false;  
  248.     }  
  249.       
  250.   
  251.       
  252. /**
  253.      * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中
  254.      * @param dismissView
  255.      * @param dismissPosition
  256.      */
  257. private void performDismiss(final View dismissView, final int
  258. final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//获取item的布局参数
  259. final int originalHeight = dismissView.getHeight();//item的高度
  260.   
  261. 0).setDuration(mAnimationTime);  
  262.         animator.start();  
  263.   
  264. new
  265. @Override
  266. public void
  267. if (onDismissCallback != null) {  
  268.                     onDismissCallback.onDismiss(dismissPosition);  
  269.                 }  
  270.   
  271. //这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0
  272. //所以我们在动画执行完毕之后将item设置回来
  273.                 ViewHelper.setAlpha(dismissView, 1f);  
  274. 0);  
  275.                 ViewGroup.LayoutParams lp = dismissView.getLayoutParams();  
  276.                 lp.height = originalHeight;  
  277.                 dismissView.setLayoutParams(lp);  
  278.   
  279.             }  
  280.         });  
  281.   
  282. new
  283. @Override
  284. public void
  285. //这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果
  286.                 lp.height = (Integer) valueAnimator.getAnimatedValue();  
  287.                 dismissView.setLayoutParams(lp);  
  288.             }  
  289.         });  
  290.   
  291.     }  
  292.   
  293. /**
  294.      * 删除的回调接口
  295.      * 
  296.      * @author xiaanming
  297.      * 
  298.      */
  299. public interface
  300. public void onDismiss(int
  301.     }  
  302.   
  303. }  

看过

​​Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果​​你会发现,这个自定义的SwipeDismissListView只重写了onTouchEvent()方法,其实我们重写这一个方法就能实现我们需要的效果

1. 我们先看手指按下屏幕的处理方法handleActionDown();该方法里面根据我们手指按下的点根据pointToPosition()方法来获取我们点击的position,然后利用getChildAt()来获取我们按下的item的View对象,并且加入手指在屏幕滑动的速度检查,这一步相对来说还是比较简单

2. 接下来就是手指在屏幕上面滑动的处理方法handleActionMove(),这个方法就稍微的复杂些,我们需要根据手指在X轴的滑动距离和Y轴的滑动距离来判断是ListView item的水平滑动还是ListView的上下滑动,当满足Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop这个条件时候,我们用一个布尔值mSwiping来标记Item现在处于水平滑动的状态,这时候我们需要处理Item跟随手指的滑动而滑动的逻辑,我们使用ViewHelper来处理Item的滑动逻辑,这个类会根据机器的SDK版本来判断使用Android系统的API还是NineOldandroids中自己实现的API使得View滑动的效果,NineOldandroids中主要使用Camera(可以实现各种复杂动画效果的类),我们直接使用ViewHelper的setTranslationX()和setAlpha()就实现了item滑动和透明度渐变的效果,为了使得我们在滑动item的时候,ListView不上下滚动,我们必须返回true来屏蔽ListView的上下滚动,这里需要我们要非常熟悉Android的事件分发机制,这里我就不说明了,大家不了解的去网上找找相关的文章看看
还有一个问题,就是当我们滑动ListView的item的时候,会伴随着item的点击事件,这不是我们想要的效果,所以当Item滑动的时候我们需要取消ListView Item的点击事件


3. 在看手指抬起的时候的处理方法handleActionUp(),这里面需要根据手指的滑动速度或者Item移动的距离来判断Item是滑出屏幕还是滑动至起始位置,并且要判断item向左还是向右滑出屏幕等等逻辑,具体的逻辑可以看代码,相信大家都看得懂.

我这里要说说ViewPropertyAnimator类,这个类能更好的实现一个View同时进行多个动画的功能,当然我们也可以使用ObjectAnimator利用AnimatorSet来实现一个View上的多个同时进行的动画效果,例如我们可以将


[java] ​​ view plain​​ ​​co



  1. ViewPropertyAnimator.animate(mDownView)  
  2. //X轴方向的移动距离
  3. 0)  
  4.         .setDuration(mAnimationTime)  
  5. new
  6. @Override
  7. public void
  8. //Item滑出界面之后执行删除
  9.                 performDismiss(mDownView, mDownPosition);  
  10.             }  
  11.         });  

替换成

[java] ​​ view plain​​ ​​cop



  1. AnimatorSet set = new
  2. "translationX", dismissRight ? mViewWidth : -mViewWidth),   
  3. "alpha", 0));  
  4.             set.setDuration(mAnimationTime).start();  
  5. new
  6. @Override
  7. public void
  8. //Item滑出界面之后执行删除
  9.                             performDismiss(mDownView, mDownPosition);  
  10.                         }  
  11.                     });  

在效果上面是一样的,但是ViewPropertyAnimator在性能上要比使用ObjectAnimator来实现多个同时进行的动画要高的多,举个例子,假如要对View使用移动和透明度的动画,使用ViewPropertyAnimator的话,某个时间点上我们只需要调用一次invalidate()方法刷新界面就行了,而使用ObjectAnimator的话,移动的动画需要调用invalidate(),透明度的动画也需要调用invalidate()方法,在性能上使用AnimationSet比ViewPropertyAnimator要低,但是有的时候我们还是需要使用ObjectAnimator,比如,在某个时间内,我们需要将View先变大在变小在变大等复杂情况,这时候ObjectAnimator就派上用场了,例如

[java] ​​ view plain​​ ​​cop



  1. ObjectAnimator.ofInt(mDownView, "scaleX", 0 ,100 ,0, 100).setDuration(100).start()  


通过上面的几步我们就实现了ListView的左右滑动删除item的效果啦,但是还有一个效果,item删除之后,ListView的其他item向上或者向下缓缓滑动的效果,实现这个也很容易,就是动态设置item的高度,item高度逐渐变小,这样其他的item就会出现向上或者向下挤压的效果啦!


4. 这里我们使用的是ValueAnimator这个类,这个类并不是针对View作用的动画,而是对某个值作用的动画,他默认使用的Interpolator(插补器)是AccelerateDecelerateInterpolator(开始和结束的时候慢,中间快) , 举个很简单的例子,我们在10秒内使用ValueAnimator将某个值从0变化到100,如果使用LinearInterpolator(线性插补器,匀速变化)在第2秒的时候,这个值变成了20,而是用AccelerateDecelerateInterpolator,可能在第二秒的时候这个值为15或者13,所以我们在ValueAnimator变化的时候设置值动画变化的监听器AnimatorUpdateListener就知道某个时间这个值变成了多少,从而对View的某个属性进行设置(例如大小),所以ValueAnimator是间接的对View设置动画的

了解了ValueAnimator的使用原理,我们就可以现实上面的动画效果了,我们使用ValueAnimator将item的高度变成0,设置ValueAnimator变化的监听,我们在回调函数onAnimationUpdate()中动态的设置item的高度, 然后添加AnimatorListener监听动画的状态(例如动画开始,结束,重复等)监听,在动画结束的回调函数onAnimationEnd()中删除该item的数据,调用notifyDataSetChanged刷新ListView,看看下面这段代码


[java] ​​ view plain​​ ​​cop



  1. ViewHelper.setAlpha(dismissView, 1f);  
  2. 0);  
  3.                 ViewGroup.LayoutParams lp = dismissView.getLayoutParams();  
  4.                 lp.height = originalHeight;  
  5.                 dismissView.setLayoutParams(lp);  

我们使用动画只是将item移动出了屏幕,并且将item的高度设置为了0,并没有将item的View从ListView中Remove掉,况且ListView也不能直接Remove掉Item的,只能将数据源删除,在调用notifyDataSetChanged()刷新,所以我们需要将刚刚滑出屏幕高度设置为0的Item恢复回来


自定义控件的代码我们已经编写完了,接下来我们就要使用它了,先看界面的布局代码


[java] ​​ view plain​​ ​​co



  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. "http://schemas.android.com/tools"
  3. "match_parent"
  4. "match_parent">    
  5.     
  6.     <com.example.swipedismisslistview.SwipeDismissListView  
  7. "@+id/swipeDismissListView"
  8. "match_parent"
  9. "match_parent"
  10. "@android:color/transparent"
  11. "@android:color/transparent">    
  12.     </com.example.swipedismisslistview.SwipeDismissListView>  
  13.     
  14. </RelativeLayout>    


很简单,一个RelativeLayout包裹我们自定义的ListView控件,接下来就是主界面的代码编写,跟平常的ListView使用一样,但是我们需要设置OnDismissCallback()监听,在

onDismiss()中删除该位置对于的数据,刷新ListView


[java] ​​ view plain​​ ​​co


  1. package
  2.   
  3. import
  4. import
  5.   
  6. import
  7. import
  8. import
  9. import
  10. import
  11. import
  12. import
  13.   
  14. import
  15.   
  16. public class SwipeActivity extends
  17. private
  18. private
  19. private List<String> dataSourceList = new
  20.   
  21. @Override
  22. protected void
  23. super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.activity_swipe);  
  25.         init();  
  26.     }  
  27.   
  28. private void
  29.         swipeDismissListView = (SwipeDismissListView) findViewById(R.id.swipeDismissListView);  
  30. for (int i = 0; i < 20; i++) {  
  31. "滑动删除"
  32.         }  
  33.   
  34. new ArrayAdapter<String>(this,  
  35.                 android.R.layout.simple_list_item_1,  
  36.                 android.R.id.text1, dataSourceList);  
  37.           
  38.         swipeDismissListView.setAdapter(adapter);  
  39.           
  40. new
  41.               
  42. @Override
  43. public void onDismiss(int
  44.                  adapter.remove(adapter.getItem(dismissPosition));   
  45.             }  
  46.         });  
  47.           
  48.           
  49. new
  50.   
  51. @Override
  52. public void
  53. int position, long
  54. this, adapter.getItem(position), Toast.LENGTH_SHORT).show();  
  55.             }  
  56.         });  
  57.   
  58.     }  
  59.   
  60. }  

所有的代码都已经编写完毕了,接下来就是运行工程,看看具体的效果是不是我们想要的

listview滑动删除_android

好了,今天的讲解到这里结束了,有了NineOldAndroids我们可以在2.x的手机上面实现许多复杂的动画效果,文章也介绍了关于开源库NineOldAndroids使用的一些知识,文章有点长,希望读者还是先将文章看下,然后自己看看能不能自己实现出来,有什么不明白的地方请在下面留言,我会为大家解答的!

​​项目源码,点击下载​​



ps: 下载源码的时候运行出错,是因为我加入了NineOldAndroids的Jar包,然后又加入了NineOldAndroids工程库,主要是我写DEMO的时候为了方便看源码就导入了NineOldAndroids工程库,大家删除一个JAR包或者NineOldAndroids工程库 就能解决了