Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之NineOldAndroids實現絢麗的ListView左右滑動刪除Item效果

Android之NineOldAndroids實現絢麗的ListView左右滑動刪除Item效果

編輯:關於Android編程

今天還是給大家帶來自定義控件的編寫,自定義一個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庫中,這是一個兼容庫,接下來我們就來看看這個效果的具體實現吧

實現該效果的主要思路

先根據手指觸摸的點來獲取點擊的是ListView的哪一個Item當手指在屏幕上面滑動的時候,我們要使得Item跟隨手指的滑動而滑動當我們抬起手指的時候,我們根據滑動的距離或者手指在屏幕上面的速度來判斷Item是滑出屏幕還是滑動至其實位置Item滑出屏幕時,使ListView的其他item產生向上擠壓或者向下擠壓的效果

大致的思路這是這四步,其中的一些細節接下來我會一一為大家解答的,接下來我們就用代碼來實現這種效果吧

首先我們新建一個工程,叫Swipedismisslistview,我們需要將Nine Old Androids這個庫引入到工程,大家可以去https://github.com/JakeWharton/NineOldAndroids下載,可以使用Jar包,也可以使用工程庫的形式引入到我們自己的工程,我們還需要自定義一個ListView,我們先看代碼然後給大家講解下具體的功能實現

  1. packagecom.example.swipedismisslistview;
  2.  
  3. importstaticcom.nineoldandroids.view.ViewHelper.setAlpha;
  4. importstaticcom.nineoldandroids.view.ViewHelper.setTranslationX;
  5. importandroid.content.Context;
  6. importandroid.util.AttributeSet;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.VelocityTracker;
  9. importandroid.view.View;
  10. importandroid.view.ViewConfiguration;
  11. importandroid.view.ViewGroup;
  12. importandroid.widget.AdapterView;
  13. importandroid.widget.ListView;
  14.  
  15. importcom.nineoldandroids.animation.Animator;
  16. importcom.nineoldandroids.animation.AnimatorListenerAdapter;
  17. importcom.nineoldandroids.animation.ValueAnimator;
  18. importcom.nineoldandroids.view.ViewHelper;
  19. importcom.nineoldandroids.view.ViewPropertyAnimator;
  20. /**
  21. *@bloghttp://blog.csdn.net/xiaanming
  22. *
  23. *@authorxiaanming
  24. *
  25. */
  26. publicclassSwipeDismissListViewextendsListView{
  27. /**
  28. *認為是用戶滑動的最小距離
  29. */
  30. privateintmSlop;
  31. /**
  32. *滑動的最小速度
  33. */
  34. privateintmMinFlingVelocity;
  35. /**
  36. *滑動的最大速度
  37. */
  38. privateintmMaxFlingVelocity;
  39. /**
  40. *執行動畫的時間
  41. */
  42. protectedlongmAnimationTime=150;
  43. /**
  44. *用來標記用戶是否正在滑動中
  45. */
  46. privatebooleanmSwiping;
  47. /**
  48. *滑動速度檢測類
  49. */
  50. privateVelocityTrackermVelocityTracker;
  51. /**
  52. *手指按下的position
  53. */
  54. privateintmDownPosition;
  55. /**
  56. *按下的item對應的View
  57. */
  58. privateViewmDownView;
  59. privatefloatmDownX;
  60. privatefloatmDownY;
  61. /**
  62. *item的寬度
  63. */
  64. privateintmViewWidth;
  65. /**
  66. *當ListView的Item滑出界面回調的接口
  67. */
  68. privateOnDismissCallbackonDismissCallback;
  69.  
  70. /**
  71. *設置動畫時間
  72. *
  73. *@parammAnimationTime
  74. */
  75. publicvoidsetmAnimationTime(longmAnimationTime){
  76. this.mAnimationTime=mAnimationTime;
  77. }
  78.  
  79. /**
  80. *設置刪除回調接口
  81. *
  82. *@paramonDismissCallback
  83. */
  84. publicvoidsetOnDismissCallback(OnDismissCallbackonDismissCallback){
  85. this.onDismissCallback=onDismissCallback;
  86. }
  87.  
  88. publicSwipeDismissListView(Contextcontext){
  89. this(context,null);
  90. }
  91.  
  92. publicSwipeDismissListView(Contextcontext,AttributeSetattrs){
  93. this(context,attrs,0);
  94. }
  95.  
  96. publicSwipeDismissListView(Contextcontext,AttributeSetattrs,
  97. intdefStyle){
  98. super(context,attrs,defStyle);
  99.  
  100. ViewConfigurationvc=ViewConfiguration.get(context);
  101. mSlop=vc.getScaledTouchSlop();
  102. mMinFlingVelocity=vc.getScaledMinimumFlingVelocity()*8;//獲取滑動的最小速度
  103. mMaxFlingVelocity=vc.getScaledMaximumFlingVelocity();//獲取滑動的最大速度
  104. }
  105.  
  106.  
  107. @Override
  108. publicbooleanonTouchEvent(MotionEventev){
  109. switch(ev.getAction()){
  110. caseMotionEvent.ACTION_DOWN:
  111. handleActionDown(ev);
  112. break;
  113. caseMotionEvent.ACTION_MOVE:
  114. returnhandleActionMove(ev);
  115. caseMotionEvent.ACTION_UP:
  116. handleActionUp(ev);
  117. break;
  118. }
  119. returnsuper.onTouchEvent(ev);
  120. }
  121.  
  122. /**
  123. *按下事件處理
  124. *
  125. *@paramev
  126. *@return
  127. */
  128. privatevoidhandleActionDown(MotionEventev){
  129. mDownX=ev.getX();
  130. mDownY=ev.getY();
  131.  
  132. mDownPosition=pointToPosition((int)mDownX,(int)mDownY);
  133.  
  134. if(mDownPosition==AdapterView.INVALID_POSITION){
  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. *@paramev
  154. *@return
  155. */
  156. privatebooleanhandleActionMove(MotionEventev){
  157. if(mVelocityTracker==null||mDownView==null){
  158. returnsuper.onTouchEvent(ev);
  159. }
  160.  
  161. //獲取X方向滑動的距離
  162. floatdeltaX=ev.getX()-mDownX;
  163. floatdeltaY=ev.getY()-mDownY;
  164.  
  165. //X方向滑動的距離大於mSlop並且Y方向滑動的距離小於mSlop,表示可以滑動
  166. if(Math.abs(deltaX)>mSlop&&Math.abs(deltaY) mSwiping=true;
  167.  
  168. //當手指滑動item,取消item的點擊事件,不然我們滑動Item也伴隨著item點擊事件的發生
  169. MotionEventcancelEvent=MotionEvent.obtain(ev);
  170. cancelEvent.setAction(MotionEvent.ACTION_CANCEL|
  171. (ev.getActionIndex()< onTouchEvent(cancelEvent);
  172. }
  173.  
  174. if(mSwiping){
  175. //跟誰手指移動item
  176. ViewHelper.setTranslationX(mDownView,deltaX);
  177. //透明度漸變
  178. ViewHelper.setAlpha(mDownView,Math.max(0f,Math.min(1f,1f-2f*Math.abs(deltaX)/mViewWidth)));
  179.  
  180. //手指滑動的時候,返回true,表示SwipeDismissListView自己處理onTouchEvent,其他的就交給父類來處理
  181. returntrue;
  182. }
  183.  
  184. returnsuper.onTouchEvent(ev);
  185.  
  186. }
  187.  
  188. /**
  189. *手指抬起的事件處理
  190. *@paramev
  191. */
  192. privatevoidhandleActionUp(MotionEventev){
  193. if(mVelocityTracker==null||mDownView==null||!mSwiping){
  194. return;
  195. }
  196.  
  197. floatdeltaX=ev.getX()-mDownX;
  198.  
  199. //通過滑動的距離計算出X,Y方向的速度
  200. mVelocityTracker.computeCurrentVelocity(1000);
  201. floatvelocityX=Math.abs(mVelocityTracker.getXVelocity());
  202. floatvelocityY=Math.abs(mVelocityTracker.getYVelocity());
  203.  
  204. booleandismiss=false;//item是否要滑出屏幕
  205. booleandismissRight=false;//是否往右邊刪除
  206.  
  207. //當拖動item的距離大於item的一半,item滑出屏幕
  208. if(Math.abs(deltaX)>mViewWidth/2){
  209. dismiss=true;
  210. dismissRight=deltaX>0;
  211.  
  212. //手指在屏幕滑動的速度在某個范圍內,也使得item滑出屏幕
  213. }elseif(mMinFlingVelocity<=velocityX
  214. &&velocityX<=mMaxFlingVelocity&&velocityY dismiss=true;
  215. dismissRight=mVelocityTracker.getXVelocity()>0;
  216. }
  217.  
  218. if(dismiss){
  219. ViewPropertyAnimator.animate(mDownView)
  220. .translationX(dismissRight?mViewWidth:-mViewWidth)//X軸方向的移動距離
  221. .alpha(0)
  222. .setDuration(mAnimationTime)
  223. .setListener(newAnimatorListenerAdapter(){
  224. @Override
  225. publicvoidonAnimationEnd(Animatoranimation){
  226. //Item滑出界面之後執行刪除
  227. performDismiss(mDownView,mDownPosition);
  228. }
  229. });
  230. }else{
  231. //將item滑動至開始位置
  232. ViewPropertyAnimator.animate(mDownView)
  233. .translationX(0)
  234. .alpha(1)
  235. .setDuration(mAnimationTime).setListener(null);
  236. }
  237.  
  238. //移除速度檢測
  239. if(mVelocityTracker!=null){
  240. mVelocityTracker.recycle();
  241. mVelocityTracker=null;
  242. }
  243.  
  244. mSwiping=false;
  245. }
  246.  
  247.  
  248.  
  249. /**
  250. *在此方法中執行item刪除之後,其他的item向上或者向下滾動的動畫,並且將position回調到方法onDismiss()中
  251. *@paramdismissView
  252. *@paramdismissPosition
  253. */
  254. privatevoidperformDismiss(finalViewdismissView,finalintdismissPosition){
  255. finalViewGroup.LayoutParamslp=dismissView.getLayoutParams();//獲取item的布局參數
  256. finalintoriginalHeight=dismissView.getHeight();//item的高度
  257.  
  258. ValueAnimatoranimator=ValueAnimator.ofInt(originalHeight,0).setDuration(mAnimationTime);
  259. animator.start();
  260.  
  261. animator.addListener(newAnimatorListenerAdapter(){
  262. @Override
  263. publicvoidonAnimationEnd(Animatoranimation){
  264. if(onDismissCallback!=null){
  265. onDismissCallback.onDismiss(dismissPosition);
  266. }
  267.  
  268. //這段代碼很重要,因為我們並沒有將item從ListView中移除,而是將item的高度設置為0
  269. //所以我們在動畫執行完畢之後將item設置回來
  270. ViewHelper.setAlpha(dismissView,1f);
  271. ViewHelper.setTranslationX(dismissView,0);
  272. ViewGroup.LayoutParamslp=dismissView.getLayoutParams();
  273. lp.height=originalHeight;
  274. dismissView.setLayoutParams(lp);
  275.  
  276. }
  277. });
  278.  
  279. animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
  280. @Override
  281. publicvoidonAnimationUpdate(ValueAnimatorvalueAnimator){
  282. //這段代碼的效果是ListView刪除某item之後,其他的item向上滑動的效果
  283. lp.height=(Integer)valueAnimator.getAnimatedValue();
  284. dismissView.setLayoutParams(lp);
  285. }
  286. });
  287.  
  288. }
  289.  
  290. /**
  291. *刪除的回調接口
  292. *
  293. *@authorxiaanming
  294. *
  295. */
  296. publicinterfaceOnDismissCallback{
  297. publicvoidonDismiss(intdismissPosition);
  298. }
  299.  
  300. }
看過Android 使用Scroller實現絢麗的ListView左右滑動刪除Item效果你會發現,這個自定義的SwipeDismissListView只重寫了onTouchEvent()方法,其實我們重寫這一個方法就能實現我們需要的效果
  1. ViewPropertyAnimator.animate(mDownView)
  2. .translationX(dismissRight?mViewWidth:-mViewWidth)//X軸方向的移動距離
  3. .alpha(0)
  4. .setDuration(mAnimationTime)
  5. .setListener(newAnimatorListenerAdapter(){
  6. @Override
  7. publicvoidonAnimationEnd(Animatoranimation){
  8. //Item滑出界面之後執行刪除
  9. performDismiss(mDownView,mDownPosition);
  10. }
  11. });
替換成 
  1. AnimatorSetset=newAnimatorSet();
  2. set.playTogether(ObjectAnimator.ofFloat(mDownView,"translationX",dismissRight?mViewWidth:-mViewWidth),
  3. ObjectAnimator.ofFloat(mDownView,"alpha",0));
  4. set.setDuration(mAnimationTime).start();
  5. set.addListener(newAnimatorListenerAdapter(){
  6. @Override
  7. publicvoidonAnimationEnd(Animatoranimation){
  8. //Item滑出界面之後執行刪除
  9. performDismiss(mDownView,mDownPosition);
  10. }
  11. });
在效果上面是一樣的,但是ViewPropertyAnimator在性能上要比使用ObjectAnimator來實現多個同時進行的動畫要高的多,舉個例子,假如要對View使用移動和透明度的動畫,使用ViewPropertyAnimator的話,某個時間點上我們只需要調用一次invalidate()方法刷新界面就行了,而使用ObjectAnimator的話,移動的動畫需要調用invalidate(),透明度的動畫也需要調用invalidate()方法,在性能上使用AnimationSet比ViewPropertyAnimator要低,但是有的時候我們還是需要使用ObjectAnimator,比如,在某個時間內,我們需要將View先變大在變小在變大等復雜情況,這時候ObjectAnimator就派上用場了,例如
  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,看看下面這段代碼

  1. ViewHelper.setAlpha(dismissView,1f);
  2. ViewHelper.setTranslationX(dismissView,0);
  3. ViewGroup.LayoutParamslp=dismissView.getLayoutParams();
  4. lp.height=originalHeight;
  5. dismissView.setLayoutParams(lp);
我們使用動畫只是將item移動出了屏幕,並且將item的高度設置為了0,並沒有將item的View從ListView中Remove掉,況且ListView也不能直接Remove掉Item的,只能將數據源刪除,在調用notifyDataSetChanged()刷新,所以我們需要將剛剛滑出屏幕高度設置為0的Item恢復回來

 

自定義控件的代碼我們已經編寫完了,接下來我們就要使用它了,先看界面的布局代碼

  1.  
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent">
  4.  
  5. android:id="@+id/swipeDismissListView"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:listSelector="@android:color/transparent"
  9. android:cacheColorHint="@android:color/transparent">
  10.  
  11.  

很簡單,一個RelativeLayout包裹我們自定義的ListView控件,接下來就是主界面的代碼編寫,跟平常的ListView使用一樣,但是我們需要設置OnDismissCallback()監聽,在

onDismiss()中刪除該位置對於的數據,刷新ListView
  1. packagecom.example.swipedismisslistview;
  2.  
  3. importjava.util.ArrayList;
  4. importjava.util.List;
  5.  
  6. importandroid.app.Activity;
  7. importandroid.os.Bundle;
  8. importandroid.view.View;
  9. importandroid.widget.AdapterView;
  10. importandroid.widget.AdapterView.OnItemClickListener;
  11. importandroid.widget.ArrayAdapter;
  12. importandroid.widget.Toast;
  13.  
  14. importcom.example.swipedismisslistview.SwipeDismissListView.OnDismissCallback;
  15.  
  16. publicclassSwipeActivityextendsActivity{
  17. privateSwipeDismissListViewswipeDismissListView;
  18. privateArrayAdapteradapter;
  19. privateListdataSourceList=newArrayList();
  20.  
  21. @Override
  22. protectedvoidonCreate(BundlesavedInstanceState){
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_swipe);
  25. init();
  26. }
  27.  
  28. privatevoidinit(){
  29. swipeDismissListView=(SwipeDismissListView)findViewById(R.id.swipeDismissListView);
  30. for(inti=0;i<20;i++){
  31. dataSourceList.add("滑動刪除"+i);
  32. }
  33.  
  34. adapter=newArrayAdapter(this,
  35. android.R.layout.simple_list_item_1,
  36. android.R.id.text1,dataSourceList);
  37.  
  38. swipeDismissListView.setAdapter(adapter);
  39.  
  40. swipeDismissListView.setOnDismissCallback(newOnDismissCallback(){
  41.  
  42. @Override
  43. publicvoidonDismiss(intdismissPosition){
  44. adapter.remove(adapter.getItem(dismissPosition));
  45. }
  46. });
  47.  
  48.  
  49. swipeDismissListView.setOnItemClickListener(newOnItemClickListener(){
  50.  
  51. @Override
  52. publicvoidonItemClick(AdapterViewparent,Viewview,
  53. intposition,longid){
  54. Toast.makeText(SwipeActivity.this,adapter.getItem(position),Toast.LENGTH_SHORT).show();
  55. }
  56. });
  57.  
  58. }
  59.  
  60. }
所有的代碼都已經編寫完畢了,接下來就是運行工程,看看具體的效果是不是我們想要的

\


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved