Android 使用Scroller實現絢麗的ListView左右滑動刪除Item效果

轉帖(http://blog.csdn.net/xiaanming/article/details/17539199

我在上一篇文章中Android 帶你從源碼的角度解析Scroller的滾動實現原理從源碼的角度介紹了Scroller的滾動實現原理,相信大家對Scroller的使用有一定的瞭解,這篇文章就給大家帶來使用Scroller的小例子,來幫助大家更加熟悉的掌握Scroller的使用,掌握好了Scroller的使用我們就能實現很多滑動的效果。例如側滑菜單,launcher,ListView的下拉刷新等等效果,我今天實現的是ListView的item的左右滑動刪除item的效果,現在很多朋友看到這個效果應該是在Android的通知欄下拉中看到這個滑動刪除的效果吧,我看到這個效果是在我之前的三星手機上左右滑動打電話發短信的效果,感覺很棒,不過現在很多手機聯繫人滑動都不是我之前那臺手機的效果啦,網上很多朋友也寫了關於滑動刪除ListView的item的例子,有些是滑動手指離開之後然後給item加向左或者向右的移動動畫,我覺得這樣子的用戶體驗不是很好,所以今天自己也寫了一個關於ListView左右滑動刪除Item的小例子,ListView的item會隨着手指在屏幕上的滑動而滑動,手指離開屏幕的時候item會根據判斷向左或者向右劃出屏幕,就是跟通知欄的效果差不多,接下來就帶大家來實現這個效果。

先說下實現該效果的主要思路

  1. 先根據手指觸摸的點來獲取點擊的是ListView的哪一個item
  2. 手指在屏幕中滑動我們利用scrollBy()來使該item跟隨手指一起滑動
  3. 手指放開的時候,我們判斷手指拖動的距離來判斷item到底是滑出屏幕還是回到開始位置
主要思路就是上面這三步,接下來我們就用代碼來實現吧,首先我們新建一個項目,叫SlideCutListView

根據需求我們需要自己自定義一個ListView來實現該功能,接下來先貼出代碼再講解具體的實現

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.example.slidecutlistview;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.view.MotionEvent;  
  6. import android.view.VelocityTracker;  
  7. import android.view.View;  
  8. import android.view.ViewConfiguration;  
  9. import android.view.WindowManager;  
  10. import android.widget.AdapterView;  
  11. import android.widget.ListView;  
  12. import android.widget.Scroller;  
  13.   
  14. /** 
  15.  * @blog http://blog.csdn.net/xiaanming 
  16.  *  
  17.  * @author xiaanming 
  18.  *  
  19.  */  
  20. public class SlideCutListView extends ListView {  
  21.     /** 
  22.      * 當前滑動的ListView position 
  23.      */  
  24.     private int slidePosition;  
  25.     /** 
  26.      * 手指按下X的座標 
  27.      */  
  28.     private int downY;  
  29.     /** 
  30.      * 手指按下Y的座標 
  31.      */  
  32.     private int downX;  
  33.     /** 
  34.      * 屏幕寬度 
  35.      */  
  36.     private int screenWidth;  
  37.     /** 
  38.      * ListView的item 
  39.      */  
  40.     private View itemView;  
  41.     /** 
  42.      * 滑動類 
  43.      */  
  44.     private Scroller scroller;  
  45.     private static final int SNAP_VELOCITY = 600;  
  46.     /** 
  47.      * 速度追蹤對象 
  48.      */  
  49.     private VelocityTracker velocityTracker;  
  50.     /** 
  51.      * 是否響應滑動,默認爲不響應 
  52.      */  
  53.     private boolean isSlide = false;  
  54.     /** 
  55.      * 認爲是用戶滑動的最小距離 
  56.      */  
  57.     private int mTouchSlop;  
  58.     /** 
  59.      *  移除item後的回調接口 
  60.      */  
  61.     private RemoveListener mRemoveListener;  
  62.     /** 
  63.      * 用來指示item滑出屏幕的方向,向左或者向右,用一個枚舉值來標記 
  64.      */  
  65.     private RemoveDirection removeDirection;  
  66.   
  67.     // 滑動刪除方向的枚舉值  
  68.     public enum RemoveDirection {  
  69.         RIGHT, LEFT;  
  70.     }  
  71.   
  72.   
  73.     public SlideCutListView(Context context) {  
  74.         this(context, null);  
  75.     }  
  76.   
  77.     public SlideCutListView(Context context, AttributeSet attrs) {  
  78.         this(context, attrs, 0);  
  79.     }  
  80.   
  81.     public SlideCutListView(Context context, AttributeSet attrs, int defStyle) {  
  82.         super(context, attrs, defStyle);  
  83.         screenWidth = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();  
  84.         scroller = new Scroller(context);  
  85.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  86.     }  
  87.       
  88.     /** 
  89.      * 設置滑動刪除的回調接口 
  90.      * @param removeListener 
  91.      */  
  92.     public void setRemoveListener(RemoveListener removeListener) {  
  93.         this.mRemoveListener = removeListener;  
  94.     }  
  95.   
  96.     /** 
  97.      * 分發事件,主要做的是判斷點擊的是那個item, 以及通過postDelayed來設置響應左右滑動事件 
  98.      */  
  99.     @Override  
  100.     public boolean dispatchTouchEvent(MotionEvent event) {  
  101.         switch (event.getAction()) {  
  102.         case MotionEvent.ACTION_DOWN: {  
  103.             addVelocityTracker(event);  
  104.   
  105.             // 假如scroller滾動還沒有結束,我們直接返回  
  106.             if (!scroller.isFinished()) {  
  107.                 return super.dispatchTouchEvent(event);  
  108.             }  
  109.             downX = (int) event.getX();  
  110.             downY = (int) event.getY();  
  111.   
  112.             slidePosition = pointToPosition(downX, downY);  
  113.   
  114.             // 無效的position, 不做任何處理  
  115.             if (slidePosition == AdapterView.INVALID_POSITION) {  
  116.                 return super.dispatchTouchEvent(event);  
  117.             }  
  118.   
  119.             // 獲取我們點擊的item view  
  120.             itemView = getChildAt(slidePosition - getFirstVisiblePosition());  
  121.             break;  
  122.         }  
  123.         case MotionEvent.ACTION_MOVE: {  
  124.             if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY  
  125.                     || (Math.abs(event.getX() - downX) > mTouchSlop && Math  
  126.                             .abs(event.getY() - downY) < mTouchSlop)) {  
  127.                 isSlide = true;  
  128.                   
  129.             }  
  130.             break;  
  131.         }  
  132.         case MotionEvent.ACTION_UP:  
  133.             recycleVelocityTracker();  
  134.             break;  
  135.         }  
  136.   
  137.         return super.dispatchTouchEvent(event);  
  138.     }  
  139.   
  140.     /** 
  141.      * 往右滑動,getScrollX()返回的是左邊緣的距離,就是以View左邊緣爲原點到開始滑動的距離,所以向右邊滑動爲負值 
  142.      */  
  143.     private void scrollRight() {  
  144.         removeDirection = RemoveDirection.RIGHT;  
  145.         final int delta = (screenWidth + itemView.getScrollX());  
  146.         // 調用startScroll方法來設置一些滾動的參數,我們在computeScroll()方法中調用scrollTo來滾動item  
  147.         scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,  
  148.                 Math.abs(delta));  
  149.         postInvalidate(); // 刷新itemView  
  150.     }  
  151.   
  152.     /** 
  153.      * 向左滑動,根據上面我們知道向左滑動爲正值 
  154.      */  
  155.     private void scrollLeft() {  
  156.         removeDirection = RemoveDirection.LEFT;  
  157.         final int delta = (screenWidth - itemView.getScrollX());  
  158.         // 調用startScroll方法來設置一些滾動的參數,我們在computeScroll()方法中調用scrollTo來滾動item  
  159.         scroller.startScroll(itemView.getScrollX(), 0, delta, 0,  
  160.                 Math.abs(delta));  
  161.         postInvalidate(); // 刷新itemView  
  162.     }  
  163.   
  164.     /** 
  165.      * 根據手指滾動itemView的距離來判斷是滾動到開始位置還是向左或者向右滾動 
  166.      */  
  167.     private void scrollByDistanceX() {  
  168.         // 如果向左滾動的距離大於屏幕的二分之一,就讓其刪除  
  169.         if (itemView.getScrollX() >= screenWidth / 2) {  
  170.             scrollLeft();  
  171.         } else if (itemView.getScrollX() <= -screenWidth / 2) {  
  172.             scrollRight();  
  173.         } else {  
  174.             // 滾回到原始位置,爲了偷下懶這裏是直接調用scrollTo滾動  
  175.             itemView.scrollTo(00);  
  176.         }  
  177.   
  178.     }  
  179.   
  180.     /** 
  181.      * 處理我們拖動ListView item的邏輯 
  182.      */  
  183.     @Override  
  184.     public boolean onTouchEvent(MotionEvent ev) {  
  185.         if (isSlide && slidePosition != AdapterView.INVALID_POSITION) {  
  186.             requestDisallowInterceptTouchEvent(true);  
  187.             addVelocityTracker(ev);  
  188.             final int action = ev.getAction();  
  189.             int x = (int) ev.getX();  
  190.             switch (action) {  
  191.             case MotionEvent.ACTION_DOWN:  
  192.                 break;  
  193.             case MotionEvent.ACTION_MOVE:  
  194.                   
  195.                 MotionEvent cancelEvent = MotionEvent.obtain(ev);  
  196.                 cancelEvent.setAction(MotionEvent.ACTION_CANCEL |  
  197.                            (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
  198.                 onTouchEvent(cancelEvent);  
  199.                   
  200.                 int deltaX = downX - x;  
  201.                 downX = x;  
  202.   
  203.                 // 手指拖動itemView滾動, deltaX大於0向左滾動,小於0向右滾  
  204.                 itemView.scrollBy(deltaX, 0);  
  205.                   
  206.                 return true;  //拖動的時候ListView不滾動  
  207.             case MotionEvent.ACTION_UP:  
  208.                 int velocityX = getScrollVelocity();  
  209.                 if (velocityX > SNAP_VELOCITY) {  
  210.                     scrollRight();  
  211.                 } else if (velocityX < -SNAP_VELOCITY) {  
  212.                     scrollLeft();  
  213.                 } else {  
  214.                     scrollByDistanceX();  
  215.                 }  
  216.                   
  217.                 recycleVelocityTracker();  
  218.                 // 手指離開的時候就不響應左右滾動  
  219.                 isSlide = false;  
  220.                 break;  
  221.             }  
  222.         }  
  223.   
  224.         //否則直接交給ListView來處理onTouchEvent事件  
  225.         return super.onTouchEvent(ev);  
  226.     }  
  227.   
  228.     @Override  
  229.     public void computeScroll() {  
  230.         // 調用startScroll的時候scroller.computeScrollOffset()返回true,  
  231.         if (scroller.computeScrollOffset()) {  
  232.             // 讓ListView item根據當前的滾動偏移量進行滾動  
  233.             itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  234.               
  235.             postInvalidate();  
  236.   
  237.             // 滾動動畫結束的時候調用回調接口  
  238.             if (scroller.isFinished()) {  
  239.                 if (mRemoveListener == null) {  
  240.                     throw new NullPointerException("RemoveListener is null, we should called setRemoveListener()");  
  241.                 }  
  242.                   
  243.                 itemView.scrollTo(00);  
  244.                 mRemoveListener.removeItem(removeDirection, slidePosition);  
  245.             }  
  246.         }  
  247.     }  
  248.   
  249.     /** 
  250.      * 添加用戶的速度跟蹤器 
  251.      *  
  252.      * @param event 
  253.      */  
  254.     private void addVelocityTracker(MotionEvent event) {  
  255.         if (velocityTracker == null) {  
  256.             velocityTracker = VelocityTracker.obtain();  
  257.         }  
  258.   
  259.         velocityTracker.addMovement(event);  
  260.     }  
  261.   
  262.     /** 
  263.      * 移除用戶速度跟蹤器 
  264.      */  
  265.     private void recycleVelocityTracker() {  
  266.         if (velocityTracker != null) {  
  267.             velocityTracker.recycle();  
  268.             velocityTracker = null;  
  269.         }  
  270.     }  
  271.   
  272.     /** 
  273.      * 獲取X方向的滑動速度,大於0向右滑動,反之向左 
  274.      *  
  275.      * @return 
  276.      */  
  277.     private int getScrollVelocity() {  
  278.         velocityTracker.computeCurrentVelocity(1000);  
  279.         int velocity = (int) velocityTracker.getXVelocity();  
  280.         return velocity;  
  281.     }  
  282.   
  283.     /** 
  284.      *  
  285.      * 當ListView item滑出屏幕,回調這個接口 
  286.      * 我們需要在回調方法removeItem()中移除該Item,然後刷新ListView 
  287.      *  
  288.      * @author xiaanming 
  289.      * 
  290.      */  
  291.     public interface RemoveListener {  
  292.         public void removeItem(RemoveDirection direction, int position);  
  293.     }  
  294.   
  295. }  
  • 首先我們重寫dispatchTouchEvent()方法,該方法是事件的分發方法,我們在裏面只做了一些簡單的步驟,我們按下屏幕的時候,如果某個item正在進行滾動,我們直接交給SlideCutListView的父類處理分發事件,否則根據點擊的X,Y座標利用pointToPosition(int x, int y)來獲取點擊的是ListView的哪一個position,從而獲取到我們需要滑動的item的View,我們還在該方法加入了滑動速度的檢測,並且在ACTION_MOVE的時候來判斷是否響應item的左右移動,用isSlide來記錄是否響應左右滑動
  • 然後就是重寫onTouchEvent()方法,我們先判斷isSlide爲true,並且我們點擊的是ListView上面的有效的position,否則直接交給SlideCutListView的父類也就是ListView來處理,在ACTION_MOVE中調用scrollBy()來移動item,scrollBy()是相對item的上一個位置進行移動的,所以我們每次都要用現在移動的距離減去上一個位置的距離然後賦給scrollBy()方法,這樣子我們就實現了item的平滑移動,當我們將手指擡起的時候,我們先根據手指滑動的速度來確定是item是滑出屏幕還是滑動至原始位置,如果向右的速度大於我們設置的SNAP_VELOCITY,item就調用scrollRight()方法滾出屏幕,如果向左的速度小於-SNAP_VELOCITY,則調用scrollLeft()向左滾出屏幕,如果我們是緩慢的移動item,則調用scrollByDistanceX()方法來判斷是滾到那個位置
在scrollRight()和scrollLeft()方法中我們使用Scroller類的startScroll()方法先設置滾動的參數,然後調用postInvalidate()來刷新界面,界面刷新就會調用computeScroll()方法,我們在裏面處理滾動邏輯就行了,值得一提的是computeScroll()裏面的這段代碼
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. itemView.scrollTo(00);  

我們需要將該item滾動在(0, 0 )這個點,因爲我們只是將ListView的Item滾動出屏幕而已,並沒有將該item移除,而且我們不能手動調用removeView()來從ListView中移除該item,我們只能通過改變ListView的數據,然後通過notifyDataSetChanged()來刷新ListView,所以我們需要將其滾動至(0, 0),這裏比較關鍵。

定義好了我們左右滑動的ListView,接下來就來使用它,佈局很簡單,一個RelativeLayout包裹我們自定義的ListView

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="@android:color/darker_gray">  
  6.   
  7.     <com.example.slidecutlistview.SlideCutListView  
  8.         android:id="@+id/slideCutListView"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"   
  11.         android:listSelector="@android:color/transparent"  
  12.         android:divider="@drawable/reader_item_divider"  
  13.         android:cacheColorHint="@android:color/transparent">  
  14.     </com.example.slidecutlistview.SlideCutListView>  
  15.   
  16. </RelativeLayout>  

接下來我們來看看ListView的item的佈局

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="wrap_content" >  
  5.   
  6.     <LinearLayout  
  7.         android:layout_width="fill_parent"  
  8.         android:layout_height="wrap_content"  
  9.         android:background="@drawable/friendactivity_comment_detail_list2" >  
  10.   
  11.         <TextView  
  12.             android:id="@+id/list_item"  
  13.             android:layout_width="match_parent"  
  14.             android:layout_height="wrap_content"  
  15.             android:layout_margin="15dip" />  
  16.     </LinearLayout>  
  17.   
  18. </LinearLayout>  
還記得我在上一篇文章中提到過調用scrollTo()方法是對裏面的子View進行滾動的,而不是對整個佈局進行滾動的,所以我們用LinearLayout來套住我們的item的佈局,這點需要注意一下,不然滾動的只是TextView。

主頁面MainActivity裏面的代碼比較簡單,裏面使用的也是ArrayAdapter,相信大家都能看懂

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.example.slidecutlistview;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.AdapterView;  
  10. import android.widget.AdapterView.OnItemClickListener;  
  11. import android.widget.ArrayAdapter;  
  12. import android.widget.Toast;  
  13.   
  14. import com.example.slidecutlistview.SlideCutListView.RemoveDirection;  
  15. import com.example.slidecutlistview.SlideCutListView.RemoveListener;  
  16.   
  17. public class MainActivity extends Activity implements RemoveListener{  
  18.     private SlideCutListView slideCutListView ;  
  19.     private ArrayAdapter<String> adapter;  
  20.     private List<String> dataSourceList = new ArrayList<String>();  
  21.   
  22.     @Override  
  23.     protected void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.         init();  
  27.     }  
  28.   
  29.     private void init() {  
  30.         slideCutListView = (SlideCutListView) findViewById(R.id.slideCutListView);  
  31.         slideCutListView.setRemoveListener(this);  
  32.           
  33.         for(int i=0; i<20; i++){  
  34.             dataSourceList.add("滑動刪除" + i);   
  35.         }  
  36.           
  37.         adapter = new ArrayAdapter<String>(this, R.layout.listview_item, R.id.list_item, dataSourceList);  
  38.         slideCutListView.setAdapter(adapter);  
  39.           
  40.         slideCutListView.setOnItemClickListener(new OnItemClickListener() {  
  41.   
  42.             @Override  
  43.             public void onItemClick(AdapterView<?> parent, View view,  
  44.                     int position, long id) {  
  45.                 Toast.makeText(MainActivity.this, dataSourceList.get(position), Toast.LENGTH_SHORT).show();  
  46.             }  
  47.         });  
  48.     }  
  49.   
  50.       
  51.     //滑動刪除之後的回調方法  
  52.     @Override  
  53.     public void removeItem(RemoveDirection direction, int position) {  
  54.         adapter.remove(adapter.getItem(position));  
  55.         switch (direction) {  
  56.         case RIGHT:  
  57.             Toast.makeText(this, "向右刪除  "+ position, Toast.LENGTH_SHORT).show();  
  58.             break;  
  59.         case LEFT:  
  60.             Toast.makeText(this, "向左刪除  "+ position, Toast.LENGTH_SHORT).show();  
  61.             break;  
  62.   
  63.         default:  
  64.             break;  
  65.         }  
  66.           
  67.     }     
  68.   
  69.   
  70. }  

這裏面需要對SlideCutListView設置RemoveListener,然後我們在回調方法removeItem(RemoveDirection direction, int position)中刪除該position的數據,在調用notifyDataSetChanged()刷新ListView,我這裏用的是ArrayAdatper,直接調用remove()就可以了。

所有的代碼就編寫完了,我們來運行下程序看看效果吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章