欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Android中實(shí)現(xiàn)ListView左右滑動(dòng)刪除條目的方法

 更新時(shí)間:2016年04月05日 14:41:14   作者:xiaanming  
這篇文章主要介紹了Android中實(shí)現(xiàn)ListView左右滑動(dòng)刪除條目的方法,文中分別展示了通過(guò)Scroller和NineOldAndroids來(lái)實(shí)現(xiàn)的例子,需要的朋友可以參考下

使用Scroller實(shí)現(xiàn)絢麗的ListView左右滑動(dòng)刪除Item效果
這里來(lái)給大家?guī)?lái)使用Scroller的小例子,同時(shí)也能用來(lái)幫助初步解除的讀者更加熟悉的掌握Scroller的使用,掌握好了Scroller的使用我們就能實(shí)現(xiàn)很多滑動(dòng)的效果。例如側(cè)滑菜單,launcher,ListView的下拉刷新等等效果,我今天實(shí)現(xiàn)的是ListView的item的左右滑動(dòng)刪除item的效果,現(xiàn)在很多朋友看到這個(gè)效果應(yīng)該是在Android的通知欄下拉中看到這個(gè)滑動(dòng)刪除的效果吧,我看到這個(gè)效果是在我之前的三星手機(jī)上左右滑動(dòng)打電話發(fā)短信的效果,感覺(jué)很棒,不過(guò)現(xiàn)在很多手機(jī)聯(lián)系人滑動(dòng)都不是我之前那臺(tái)手機(jī)的效果啦,網(wǎng)上很多朋友也寫了關(guān)于滑動(dòng)刪除ListView的item的例子,有些是滑動(dòng)手指離開(kāi)之后然后給item加向左或者向右的移動(dòng)動(dòng)畫,我覺(jué)得這樣子的用戶體驗(yàn)不是很好,所以今天自己也寫了一個(gè)關(guān)于ListView左右滑動(dòng)刪除Item的小例子,ListView的item會(huì)隨著手指在屏幕上的滑動(dòng)而滑動(dòng),手指離開(kāi)屏幕的時(shí)候item會(huì)根據(jù)判斷向左或者向右劃出屏幕,就是跟通知欄的效果差不多,接下來(lái)就帶大家來(lái)實(shí)現(xiàn)這個(gè)效果。
先說(shuō)下實(shí)現(xiàn)該效果的主要思路
先根據(jù)手指觸摸的點(diǎn)來(lái)獲取點(diǎn)擊的是ListView的哪一個(gè)item
手指在屏幕中滑動(dòng)我們利用scrollBy()來(lái)使該item跟隨手指一起滑動(dòng)
手指放開(kāi)的時(shí)候,我們判斷手指拖動(dòng)的距離來(lái)判斷item到底是滑出屏幕還是回到開(kāi)始位置
主要思路就是上面這三步,接下來(lái)我們就用代碼來(lái)實(shí)現(xiàn)吧,首先我們新建一個(gè)項(xiàng)目,叫SlideCutListView
根據(jù)需求我們需要自己自定義一個(gè)ListView來(lái)實(shí)現(xiàn)該功能,接下來(lái)先貼出代碼再講解具體的實(shí)現(xiàn)

package com.example.slidecutlistview; 
 
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; 

public class SlideCutListView extends ListView { 
  /** 
   * 當(dāng)前滑動(dòng)的ListView position 
   */ 
  private int slidePosition; 
  /** 
   * 手指按下X的坐標(biāo) 
   */ 
  private int downY; 
  /** 
   * 手指按下Y的坐標(biāo) 
   */ 
  private int downX; 
  /** 
   * 屏幕寬度 
   */ 
  private int screenWidth; 
  /** 
   * ListView的item 
   */ 
  private View itemView; 
  /** 
   * 滑動(dòng)類 
   */ 
  private Scroller scroller; 
  private static final int SNAP_VELOCITY = 600; 
  /** 
   * 速度追蹤對(duì)象 
   */ 
  private VelocityTracker velocityTracker; 
  /** 
   * 是否響應(yīng)滑動(dòng),默認(rèn)為不響應(yīng) 
   */ 
  private boolean isSlide = false; 
  /** 
   * 認(rèn)為是用戶滑動(dòng)的最小距離 
   */ 
  private int mTouchSlop; 
  /** 
   * 移除item后的回調(diào)接口 
   */ 
  private RemoveListener mRemoveListener; 
  /** 
   * 用來(lái)指示item滑出屏幕的方向,向左或者向右,用一個(gè)枚舉值來(lái)標(biāo)記 
   */ 
  private RemoveDirection removeDirection; 
 
  // 滑動(dòng)刪除方向的枚舉值 
  public enum RemoveDirection { 
    RIGHT, LEFT; 
  } 
 
 
  public SlideCutListView(Context context) { 
    this(context, null); 
  } 
 
  public SlideCutListView(Context context, AttributeSet attrs) { 
    this(context, attrs, 0); 
  } 
 
  public SlideCutListView(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(); 
  } 
   
  /** 
   * 設(shè)置滑動(dòng)刪除的回調(diào)接口 
   * @param removeListener 
   */ 
  public void setRemoveListener(RemoveListener removeListener) { 
    this.mRemoveListener = removeListener; 
  } 
 
  /** 
   * 分發(fā)事件,主要做的是判斷點(diǎn)擊的是那個(gè)item, 以及通過(guò)postDelayed來(lái)設(shè)置響應(yīng)左右滑動(dòng)事件 
   */ 
  @Override 
  public boolean dispatchTouchEvent(MotionEvent event) { 
    switch (event.getAction()) { 
    case MotionEvent.ACTION_DOWN: { 
      addVelocityTracker(event); 
 
      // 假如scroller滾動(dòng)還沒(méi)有結(jié)束,我們直接返回 
      if (!scroller.isFinished()) { 
        return super.dispatchTouchEvent(event); 
      } 
      downX = (int) event.getX(); 
      downY = (int) event.getY(); 
 
      slidePosition = pointToPosition(downX, downY); 
 
      // 無(wú)效的position, 不做任何處理 
      if (slidePosition == AdapterView.INVALID_POSITION) { 
        return super.dispatchTouchEvent(event); 
      } 
 
      // 獲取我們點(diǎn)擊的item view 
      itemView = getChildAt(slidePosition - getFirstVisiblePosition()); 
      break; 
    } 
    case MotionEvent.ACTION_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); 
  } 
 
  /** 
   * 往右滑動(dòng),getScrollX()返回的是左邊緣的距離,就是以View左邊緣為原點(diǎn)到開(kāi)始滑動(dòng)的距離,所以向右邊滑動(dòng)為負(fù)值 
   */ 
  private void scrollRight() { 
    removeDirection = RemoveDirection.RIGHT; 
    final int delta = (screenWidth + itemView.getScrollX()); 
    // 調(diào)用startScroll方法來(lái)設(shè)置一些滾動(dòng)的參數(shù),我們?cè)赾omputeScroll()方法中調(diào)用scrollTo來(lái)滾動(dòng)item 
    scroller.startScroll(itemView.getScrollX(), 0, -delta, 0, 
        Math.abs(delta)); 
    postInvalidate(); // 刷新itemView 
  } 
 
  /** 
   * 向左滑動(dòng),根據(jù)上面我們知道向左滑動(dòng)為正值 
   */ 
  private void scrollLeft() { 
    removeDirection = RemoveDirection.LEFT; 
    final int delta = (screenWidth - itemView.getScrollX()); 
    // 調(diào)用startScroll方法來(lái)設(shè)置一些滾動(dòng)的參數(shù),我們?cè)赾omputeScroll()方法中調(diào)用scrollTo來(lái)滾動(dòng)item 
    scroller.startScroll(itemView.getScrollX(), 0, delta, 0, 
        Math.abs(delta)); 
    postInvalidate(); // 刷新itemView 
  } 
 
  /** 
   * 根據(jù)手指滾動(dòng)itemView的距離來(lái)判斷是滾動(dòng)到開(kāi)始位置還是向左或者向右滾動(dòng) 
   */ 
  private void scrollByDistanceX() { 
    // 如果向左滾動(dòng)的距離大于屏幕的二分之一,就讓其刪除 
    if (itemView.getScrollX() >= screenWidth / 2) { 
      scrollLeft(); 
    } else if (itemView.getScrollX() <= -screenWidth / 2) { 
      scrollRight(); 
    } else { 
      // 滾回到原始位置,為了偷下懶這里是直接調(diào)用scrollTo滾動(dòng) 
      itemView.scrollTo(0, 0); 
    } 
 
  } 
 
  /** 
   * 處理我們拖動(dòng)ListView item的邏輯 
   */ 
  @Override 
  public boolean onTouchEvent(MotionEvent ev) { 
    if (isSlide && slidePosition != AdapterView.INVALID_POSITION) { 
      requestDisallowInterceptTouchEvent(true); 
      addVelocityTracker(ev); 
      final int action = ev.getAction(); 
      int x = (int) ev.getX(); 
      switch (action) { 
      case MotionEvent.ACTION_DOWN: 
        break; 
      case MotionEvent.ACTION_MOVE: 
         
        MotionEvent cancelEvent = MotionEvent.obtain(ev); 
        cancelEvent.setAction(MotionEvent.ACTION_CANCEL | 
              (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT)); 
        onTouchEvent(cancelEvent); 
         
        int deltaX = downX - x; 
        downX = x; 
 
        // 手指拖動(dòng)itemView滾動(dòng), deltaX大于0向左滾動(dòng),小于0向右滾 
        itemView.scrollBy(deltaX, 0); 
         
        return true; //拖動(dòng)的時(shí)候ListView不滾動(dòng) 
      case MotionEvent.ACTION_UP: 
        int velocityX = getScrollVelocity(); 
        if (velocityX > SNAP_VELOCITY) { 
          scrollRight(); 
        } else if (velocityX < -SNAP_VELOCITY) { 
          scrollLeft(); 
        } else { 
          scrollByDistanceX(); 
        } 
         
        recycleVelocityTracker(); 
        // 手指離開(kāi)的時(shí)候就不響應(yīng)左右滾動(dòng) 
        isSlide = false; 
        break; 
      } 
    } 
 
    //否則直接交給ListView來(lái)處理onTouchEvent事件 
    return super.onTouchEvent(ev); 
  } 
 
  @Override 
  public void computeScroll() { 
    // 調(diào)用startScroll的時(shí)候scroller.computeScrollOffset()返回true, 
    if (scroller.computeScrollOffset()) { 
      // 讓ListView item根據(jù)當(dāng)前的滾動(dòng)偏移量進(jìn)行滾動(dòng) 
      itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY()); 
       
      postInvalidate(); 
 
      // 滾動(dòng)動(dòng)畫結(jié)束的時(shí)候調(diào)用回調(diào)接口 
      if (scroller.isFinished()) { 
        if (mRemoveListener == null) { 
          throw new NullPointerException("RemoveListener is null, we should called setRemoveListener()"); 
        } 
         
        itemView.scrollTo(0, 0); 
        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方向的滑動(dòng)速度,大于0向右滑動(dòng),反之向左 
   * 
   * @return 
   */ 
  private int getScrollVelocity() { 
    velocityTracker.computeCurrentVelocity(1000); 
    int velocity = (int) velocityTracker.getXVelocity(); 
    return velocity; 
  } 
 
  /** 
   * 
   * 當(dāng)ListView item滑出屏幕,回調(diào)這個(gè)接口 
   * 我們需要在回調(diào)方法removeItem()中移除該Item,然后刷新ListView 
   * 
   * @author xiaanming 
   * 
   */ 
  public interface RemoveListener { 
    public void removeItem(RemoveDirection direction, int position); 
  } 
 
} 

首先我們重寫dispatchTouchEvent()方法,該方法是事件的分發(fā)方法,我們?cè)诶锩嬷蛔隽艘恍┖?jiǎn)單的步驟,我們按下屏幕的時(shí)候,如果某個(gè)item正在進(jìn)行滾動(dòng),我們直接交給SlideCutListView的父類處理分發(fā)事件,否則根據(jù)點(diǎn)擊的X,Y坐標(biāo)利用pointToPosition(int x, int y)來(lái)獲取點(diǎn)擊的是ListView的哪一個(gè)position,從而獲取到我們需要滑動(dòng)的item的View,我們還在該方法加入了滑動(dòng)速度的檢測(cè),并且在ACTION_MOVE的時(shí)候來(lái)判斷是否響應(yīng)item的左右移動(dòng),用isSlide來(lái)記錄是否響應(yīng)左右滑動(dòng)
然后就是重寫onTouchEvent()方法,我們先判斷isSlide為true,并且我們點(diǎn)擊的是ListView上面的有效的position,否則直接交給SlideCutListView的父類也就是ListView來(lái)處理,在ACTION_MOVE中調(diào)用scrollBy()來(lái)移動(dòng)item,scrollBy()是相對(duì)item的上一個(gè)位置進(jìn)行移動(dòng)的,所以我們每次都要用現(xiàn)在移動(dòng)的距離減去上一個(gè)位置的距離然后賦給scrollBy()方法,這樣子我們就實(shí)現(xiàn)了item的平滑移動(dòng),當(dāng)我們將手指抬起的時(shí)候,我們先根據(jù)手指滑動(dòng)的速度來(lái)確定是item是滑出屏幕還是滑動(dòng)至原始位置,如果向右的速度大于我們?cè)O(shè)置的SNAP_VELOCITY,item就調(diào)用scrollRight()方法滾出屏幕,如果向左的速度小于-SNAP_VELOCITY,則調(diào)用scrollLeft()向左滾出屏幕,如果我們是緩慢的移動(dòng)item,則調(diào)用scrollByDistanceX()方法來(lái)判斷是滾到那個(gè)位置
在scrollRight()和scrollLeft()方法中我們使用Scroller類的startScroll()方法先設(shè)置滾動(dòng)的參數(shù),然后調(diào)用postInvalidate()來(lái)刷新界面,界面刷新就會(huì)調(diào)用computeScroll()方法,我們?cè)诶锩嫣幚頋L動(dòng)邏輯就行了,值得一提的是computeScroll()里面的這段代碼

itemView.scrollTo(0, 0); 

我們需要將該item滾動(dòng)在(0, 0 )這個(gè)點(diǎn),因?yàn)槲覀冎皇菍istView的Item滾動(dòng)出屏幕而已,并沒(méi)有將該item移除,而且我們不能手動(dòng)調(diào)用removeView()來(lái)從ListView中移除該item,我們只能通過(guò)改變ListView的數(shù)據(jù),然后通過(guò)notifyDataSetChanged()來(lái)刷新ListView,所以我們需要將其滾動(dòng)至(0, 0),這里比較關(guān)鍵。
定義好了我們左右滑動(dòng)的ListView,接下來(lái)就來(lái)使用它,布局很簡(jiǎn)單,一個(gè)RelativeLayout包裹我們自定義的ListView

<RelativeLayout 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:background="@android:color/darker_gray"> 
 
  <com.example.slidecutlistview.SlideCutListView 
    android:id="@+id/slideCutListView" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"  
    android:listSelector="@android:color/transparent" 
    android:divider="@drawable/reader_item_divider" 
    android:cacheColorHint="@android:color/transparent"> 
  </com.example.slidecutlistview.SlideCutListView> 
 
</RelativeLayout> 

接下來(lái)我們來(lái)看看ListView的item的布局

<?xml version="1.0" encoding="UTF-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:layout_width="fill_parent" 
  android:layout_height="wrap_content" > 
 
  <LinearLayout 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:background="@drawable/friendactivity_comment_detail_list2" > 
 
    <TextView 
      android:id="@+id/list_item" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:layout_margin="15dip" /> 
  </LinearLayout> 
 
</LinearLayout> 

還記得我在上一篇文章中提到過(guò)調(diào)用scrollTo()方法是對(duì)里面的子View進(jìn)行滾動(dòng)的,而不是對(duì)整個(gè)布局進(jìn)行滾動(dòng)的,所以我們用LinearLayout來(lái)套住我們的item的布局,這點(diǎn)需要注意一下,不然滾動(dòng)的只是TextView。
主頁(yè)面MainActivity里面的代碼比較簡(jiǎn)單,里面使用的也是ArrayAdapter,相信大家都能看懂

package com.example.slidecutlistview; 
 
import java.util.ArrayList; 
import java.util.List; 
 
import android.app.Activity; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.AdapterView; 
import android.widget.AdapterView.OnItemClickListener; 
import android.widget.ArrayAdapter; 
import android.widget.Toast; 
 
import com.example.slidecutlistview.SlideCutListView.RemoveDirection; 
import com.example.slidecutlistview.SlideCutListView.RemoveListener; 
 
public class MainActivity extends Activity implements RemoveListener{ 
  private SlideCutListView slideCutListView ; 
  private ArrayAdapter<String> adapter; 
  private List<String> dataSourceList = new ArrayList<String>(); 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    init(); 
  } 
 
  private void init() { 
    slideCutListView = (SlideCutListView) findViewById(R.id.slideCutListView); 
    slideCutListView.setRemoveListener(this); 
     
    for(int i=0; i<20; i++){ 
      dataSourceList.add("滑動(dòng)刪除" + i);  
    } 
     
    adapter = new ArrayAdapter<String>(this, R.layout.listview_item, R.id.list_item, dataSourceList); 
    slideCutListView.setAdapter(adapter); 
     
    slideCutListView.setOnItemClickListener(new OnItemClickListener() { 
 
      @Override 
      public void onItemClick(AdapterView<?> parent, View view, 
          int position, long id) { 
        Toast.makeText(MainActivity.this, dataSourceList.get(position), Toast.LENGTH_SHORT).show(); 
      } 
    }); 
  } 
 
   
  //滑動(dòng)刪除之后的回調(diào)方法 
  @Override 
  public void removeItem(RemoveDirection direction, int position) { 
    adapter.remove(adapter.getItem(position)); 
    switch (direction) { 
    case RIGHT: 
      Toast.makeText(this, "向右刪除 "+ position, Toast.LENGTH_SHORT).show(); 
      break; 
    case LEFT: 
      Toast.makeText(this, "向左刪除 "+ position, Toast.LENGTH_SHORT).show(); 
      break; 
 
    default: 
      break; 
    } 
     
  }   
 
 
} 

這里面需要對(duì)SlideCutListView設(shè)置RemoveListener,然后我們?cè)诨卣{(diào)方法removeItem(RemoveDirection direction, int position)中刪除該position的數(shù)據(jù),在調(diào)用notifyDataSetChanged()刷新ListView,我這里用的是ArrayAdatper,直接調(diào)用remove()就可以了。
所有的代碼就編寫完了,我們來(lái)運(yùn)行下程序看看效果吧

使用NineOldAndroids實(shí)現(xiàn)絢麗的ListView左右滑動(dòng)刪除Item效果
再給大家來(lái)一個(gè)ListView左右滑動(dòng)刪除Item效果的例子,上面使用的是滑動(dòng)類Scroller來(lái)實(shí)現(xiàn)的,但是看了下通知欄的左右滑動(dòng)刪除效果,確實(shí)很棒,當(dāng)我們滑動(dòng)Item超過(guò)一半的時(shí)候,item的透明度就變成了0,我們就知道抬起手指的時(shí)候item就被刪除了,當(dāng)item的透明度不為0的時(shí)候,我們抬起手指Item會(huì)回到起始位置,這樣我們就知道拖動(dòng)到什么位置item會(huì)刪除,什么位置Item不刪除,用戶體驗(yàn)更好了,還有一個(gè)效果,就是我們滑動(dòng)刪除了item的時(shí)候,ListView的其他item會(huì)出現(xiàn)向上或者向下滾動(dòng)的效果,感覺(jué)效果很棒,所以在GitHub上面搜索了下,發(fā)現(xiàn)很多開(kāi)源庫(kù)都有這個(gè)效果,比如ListViewAnimations, android-swipelistview等等,我看了下實(shí)現(xiàn)原理,使用的是Jake Wharton的動(dòng)畫開(kāi)源庫(kù)NineOldAndroids,這個(gè)庫(kù)究竟是干嘛的呢?在API3.0(Honeycomb), SDK新增了一個(gè)android.animation包,里面的類是實(shí)現(xiàn)動(dòng)畫效果相關(guān)的類,通過(guò)Honeycomb API,能夠?qū)崿F(xiàn)非常復(fù)雜的動(dòng)畫效果,但是如果開(kāi)發(fā)者想在3.0以下使用這一套API, 則需要使用開(kāi)源框架Nine Old Androids,在這個(gè)庫(kù)中會(huì)根據(jù)我們運(yùn)行的機(jī)器判斷其SDK版本,如果是API3.0以上則使用Android自帶的動(dòng)畫類,否則就使用Nine Old Androids庫(kù)中,這是一個(gè)兼容庫(kù),接下來(lái)我們就來(lái)看看這個(gè)效果的具體實(shí)現(xiàn)吧
實(shí)現(xiàn)該效果的主要思路
先根據(jù)手指觸摸的點(diǎn)來(lái)獲取點(diǎn)擊的是ListView的哪一個(gè)Item
當(dāng)手指在屏幕上面滑動(dòng)的時(shí)候,我們要使得Item跟隨手指的滑動(dòng)而滑動(dòng)
當(dāng)我們抬起手指的時(shí)候,我們根據(jù)滑動(dòng)的距離或者手指在屏幕上面的速度來(lái)判斷Item是滑出屏幕還是滑動(dòng)至其實(shí)位置
Item滑出屏幕時(shí),使ListView的其他item產(chǎn)生向上擠壓或者向下擠壓的效果
大致的思路這是這四步,其中的一些細(xì)節(jié)接下來(lái)我會(huì)一一為大家解答的,接下來(lái)我們就用代碼來(lái)實(shí)現(xiàn)這種效果吧
首先我們新建一個(gè)工程,叫Swipedismisslistview,我們需要將Nine Old Androids這個(gè)庫(kù)引入到工程,大家可以去https://github.com/JakeWharton/NineOldAndroids下載,可以使用Jar包,也可以使用工程庫(kù)的形式引入到我們自己的工程,我們還需要自定義一個(gè)ListView,我們先看代碼然后給大家講解下具體的功能實(shí)現(xiàn)

package com.example.swipedismisslistview; 
 
import static com.nineoldandroids.view.ViewHelper.setAlpha; 
import static com.nineoldandroids.view.ViewHelper.setTranslationX; 
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.ViewGroup; 
import android.widget.AdapterView; 
import android.widget.ListView; 
 
import com.nineoldandroids.animation.Animator; 
import com.nineoldandroids.animation.AnimatorListenerAdapter; 
import com.nineoldandroids.animation.ValueAnimator; 
import com.nineoldandroids.view.ViewHelper; 
import com.nineoldandroids.view.ViewPropertyAnimator; 

public class SwipeDismissListView extends ListView { 
  /** 
   * 認(rèn)為是用戶滑動(dòng)的最小距離 
   */ 
  private int mSlop; 
  /** 
   * 滑動(dòng)的最小速度 
   */ 
  private int mMinFlingVelocity; 
  /** 
   * 滑動(dòng)的最大速度 
   */ 
  private int mMaxFlingVelocity; 
  /** 
   * 執(zhí)行動(dòng)畫的時(shí)間 
   */ 
  protected long mAnimationTime = 150; 
  /** 
   * 用來(lái)標(biāo)記用戶是否正在滑動(dòng)中 
   */ 
  private boolean mSwiping; 
  /** 
   * 滑動(dòng)速度檢測(cè)類 
   */ 
  private VelocityTracker mVelocityTracker; 
  /** 
   * 手指按下的position 
   */ 
  private int mDownPosition; 
  /** 
   * 按下的item對(duì)應(yīng)的View 
   */ 
  private View mDownView; 
  private float mDownX; 
  private float mDownY; 
  /** 
   * item的寬度 
   */ 
  private int mViewWidth; 
  /** 
   * 當(dāng)ListView的Item滑出界面回調(diào)的接口 
   */ 
  private OnDismissCallback onDismissCallback; 
 
  /** 
   * 設(shè)置動(dòng)畫時(shí)間 
   * 
   * @param mAnimationTime 
   */ 
  public void setmAnimationTime(long mAnimationTime) { 
    this.mAnimationTime = mAnimationTime; 
  } 
 
  /** 
   * 設(shè)置刪除回調(diào)接口 
   * 
   * @param onDismissCallback 
   */ 
  public void setOnDismissCallback(OnDismissCallback onDismissCallback) { 
    this.onDismissCallback = onDismissCallback; 
  } 
 
  public SwipeDismissListView(Context context) { 
    this(context, null); 
  } 
 
  public SwipeDismissListView(Context context, AttributeSet attrs) { 
    this(context, attrs, 0); 
  } 
 
  public SwipeDismissListView(Context context, AttributeSet attrs, 
      int defStyle) { 
    super(context, attrs, defStyle); 
 
    ViewConfiguration vc = ViewConfiguration.get(context); 
    mSlop = vc.getScaledTouchSlop(); 
    mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 8; //獲取滑動(dòng)的最小速度 
    mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); //獲取滑動(dòng)的最大速度 
  } 
 
   
  @Override 
  public boolean onTouchEvent(MotionEvent ev) { 
    switch (ev.getAction()) { 
    case MotionEvent.ACTION_DOWN: 
      handleActionDown(ev); 
      break; 
    case MotionEvent.ACTION_MOVE: 
      return handleActionMove(ev); 
    case MotionEvent.ACTION_UP: 
      handleActionUp(ev); 
      break; 
    } 
    return super.onTouchEvent(ev); 
  } 
 
  /** 
   * 按下事件處理 
   * 
   * @param ev 
   * @return 
   */ 
  private void handleActionDown(MotionEvent ev) { 
    mDownX = ev.getX(); 
    mDownY = ev.getY(); 
     
    mDownPosition = pointToPosition((int) mDownX, (int) mDownY); 
 
    if (mDownPosition == AdapterView.INVALID_POSITION) { 
      return; 
    } 
 
    mDownView = getChildAt(mDownPosition - getFirstVisiblePosition()); 
 
    if (mDownView != null) { 
      mViewWidth = mDownView.getWidth(); 
    } 
 
    //加入速度檢測(cè) 
    mVelocityTracker = VelocityTracker.obtain(); 
    mVelocityTracker.addMovement(ev); 
  } 
   
 
  /** 
   * 處理手指滑動(dòng)的方法 
   * 
   * @param ev 
   * @return 
   */ 
  private boolean handleActionMove(MotionEvent ev) { 
    if (mVelocityTracker == null || mDownView == null) { 
      return super.onTouchEvent(ev); 
    } 
 
    // 獲取X方向滑動(dòng)的距離 
    float deltaX = ev.getX() - mDownX; 
    float deltaY = ev.getY() - mDownY; 
 
    // X方向滑動(dòng)的距離大于mSlop并且Y方向滑動(dòng)的距離小于mSlop,表示可以滑動(dòng) 
    if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) { 
      mSwiping = true; 
       
      //當(dāng)手指滑動(dòng)item,取消item的點(diǎn)擊事件,不然我們滑動(dòng)Item也伴隨著item點(diǎn)擊事件的發(fā)生 
      MotionEvent cancelEvent = MotionEvent.obtain(ev); 
      cancelEvent.setAction(MotionEvent.ACTION_CANCEL | 
            (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT)); 
      onTouchEvent(cancelEvent); 
    } 
 
    if (mSwiping) { 
      // 跟誰(shuí)手指移動(dòng)item 
      ViewHelper.setTranslationX(mDownView, deltaX); 
      // 透明度漸變 
      ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX)/ mViewWidth))); 
 
      // 手指滑動(dòng)的時(shí)候,返回true,表示SwipeDismissListView自己處理onTouchEvent,其他的就交給父類來(lái)處理 
      return true; 
    } 
 
    return super.onTouchEvent(ev); 
 
  } 
 
  /** 
   * 手指抬起的事件處理 
   * @param ev 
   */ 
  private void handleActionUp(MotionEvent ev) { 
    if (mVelocityTracker == null || mDownView == null|| !mSwiping) { 
      return; 
    } 
 
    float deltaX = ev.getX() - mDownX; 
     
    //通過(guò)滑動(dòng)的距離計(jì)算出X,Y方向的速度 
    mVelocityTracker.computeCurrentVelocity(1000); 
    float velocityX = Math.abs(mVelocityTracker.getXVelocity()); 
    float velocityY = Math.abs(mVelocityTracker.getYVelocity()); 
     
    boolean dismiss = false; //item是否要滑出屏幕 
    boolean dismissRight = false;//是否往右邊刪除 
     
    //當(dāng)拖動(dòng)item的距離大于item的一半,item滑出屏幕 
    if (Math.abs(deltaX) > mViewWidth / 2) { 
      dismiss = true; 
      dismissRight = deltaX > 0; 
       
      //手指在屏幕滑動(dòng)的速度在某個(gè)范圍內(nèi),也使得item滑出屏幕 
    } else if (mMinFlingVelocity <= velocityX 
        && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { 
      dismiss = true; 
      dismissRight = mVelocityTracker.getXVelocity() > 0; 
    } 
 
    if (dismiss) { 
      ViewPropertyAnimator.animate(mDownView) 
          .translationX(dismissRight ? mViewWidth : -mViewWidth)//X軸方向的移動(dòng)距離 
          .alpha(0) 
          .setDuration(mAnimationTime) 
          .setListener(new AnimatorListenerAdapter() { 
            @Override 
            public void onAnimationEnd(Animator animation) { 
              //Item滑出界面之后執(zhí)行刪除 
              performDismiss(mDownView, mDownPosition); 
            } 
          }); 
    } else { 
      //將item滑動(dòng)至開(kāi)始位置 
      ViewPropertyAnimator.animate(mDownView) 
      .translationX(0) 
      .alpha(1)   
      .setDuration(mAnimationTime).setListener(null); 
    } 
     
    //移除速度檢測(cè) 
    if(mVelocityTracker != null){ 
      mVelocityTracker.recycle(); 
      mVelocityTracker = null; 
    } 
     
    mSwiping = false; 
  } 
   
 
   
  /** 
   * 在此方法中執(zhí)行item刪除之后,其他的item向上或者向下滾動(dòng)的動(dòng)畫,并且將position回調(diào)到方法onDismiss()中 
   * @param dismissView 
   * @param dismissPosition 
   */ 
  private void performDismiss(final View dismissView, final int dismissPosition) { 
    final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//獲取item的布局參數(shù) 
    final int originalHeight = dismissView.getHeight();//item的高度 
 
    ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 0).setDuration(mAnimationTime); 
    animator.start(); 
 
    animator.addListener(new AnimatorListenerAdapter() { 
      @Override 
      public void onAnimationEnd(Animator animation) { 
        if (onDismissCallback != null) { 
          onDismissCallback.onDismiss(dismissPosition); 
        } 
 
        //這段代碼很重要,因?yàn)槲覀儾](méi)有將item從ListView中移除,而是將item的高度設(shè)置為0 
        //所以我們?cè)趧?dòng)畫執(zhí)行完畢之后將item設(shè)置回來(lái) 
        ViewHelper.setAlpha(dismissView, 1f); 
        ViewHelper.setTranslationX(dismissView, 0); 
        ViewGroup.LayoutParams lp = dismissView.getLayoutParams(); 
        lp.height = originalHeight; 
        dismissView.setLayoutParams(lp); 
 
      } 
    }); 
 
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
      @Override 
      public void onAnimationUpdate(ValueAnimator valueAnimator) { 
        //這段代碼的效果是ListView刪除某item之后,其他的item向上滑動(dòng)的效果 
        lp.height = (Integer) valueAnimator.getAnimatedValue(); 
        dismissView.setLayoutParams(lp); 
      } 
    }); 
 
  } 
 
  /** 
   * 刪除的回調(diào)接口 
   * 
   * @author xiaanming 
   * 
   */ 
  public interface OnDismissCallback { 
    public void onDismiss(int dismissPosition); 
  } 
 
} 

看過(guò)Android 使用Scroller實(shí)現(xiàn)絢麗的ListView左右滑動(dòng)刪除Item效果你會(huì)發(fā)現(xiàn),這個(gè)自定義的SwipeDismissListView只重寫了onTouchEvent()方法,其實(shí)我們重寫這一個(gè)方法就能實(shí)現(xiàn)我們需要的效果
1. 我們先看手指按下屏幕的處理方法handleActionDown();該方法里面根據(jù)我們手指按下的點(diǎn)根據(jù)pointToPosition()方法來(lái)獲取我們點(diǎn)擊的position,然后利用getChildAt()來(lái)獲取我們按下的item的View對(duì)象,并且加入手指在屏幕滑動(dòng)的速度檢查,這一步相對(duì)來(lái)說(shuō)還是比較簡(jiǎn)單

2. 接下來(lái)就是手指在屏幕上面滑動(dòng)的處理方法handleActionMove(),這個(gè)方法就稍微的復(fù)雜些,我們需要根據(jù)手指在X軸的滑動(dòng)距離和Y軸的滑動(dòng)距離來(lái)判斷是ListView item的水平滑動(dòng)還是ListView的上下滑動(dòng),當(dāng)滿足Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop這個(gè)條件時(shí)候,我們用一個(gè)布爾值mSwiping來(lái)標(biāo)記Item現(xiàn)在處于水平滑動(dòng)的狀態(tài),這時(shí)候我們需要處理Item跟隨手指的滑動(dòng)而滑動(dòng)的邏輯,我們使用ViewHelper來(lái)處理Item的滑動(dòng)邏輯,這個(gè)類會(huì)根據(jù)機(jī)器的SDK版本來(lái)判斷使用Android系統(tǒng)的API還是NineOldandroids中自己實(shí)現(xiàn)的API使得View滑動(dòng)的效果,NineOldandroids中主要使用Camera(可以實(shí)現(xiàn)各種復(fù)雜動(dòng)畫效果的類),我們直接使用ViewHelper的setTranslationX()和setAlpha()就實(shí)現(xiàn)了item滑動(dòng)和透明度漸變的效果,為了使得我們?cè)诨瑒?dòng)item的時(shí)候,ListView不上下滾動(dòng),我們必須返回true來(lái)屏蔽ListView的上下滾動(dòng),這里需要我們要非常熟悉Android的事件分發(fā)機(jī)制,這里我就不說(shuō)明了,大家不了解的去網(wǎng)上找找相關(guān)的文章看看
還有一個(gè)問(wèn)題,就是當(dāng)我們滑動(dòng)ListView的item的時(shí)候,會(huì)伴隨著item的點(diǎn)擊事件,這不是我們想要的效果,所以當(dāng)Item滑動(dòng)的時(shí)候我們需要取消ListView Item的點(diǎn)擊事件

3. 在看手指抬起的時(shí)候的處理方法handleActionUp(),這里面需要根據(jù)手指的滑動(dòng)速度或者Item移動(dòng)的距離來(lái)判斷Item是滑出屏幕還是滑動(dòng)至起始位置,并且要判斷item向左還是向右滑出屏幕等等邏輯,具體的邏輯可以看代碼,相信大家都看得懂.
我這里要說(shuō)說(shuō)ViewPropertyAnimator類,這個(gè)類能更好的實(shí)現(xiàn)一個(gè)View同時(shí)進(jìn)行多個(gè)動(dòng)畫的功能,當(dāng)然我們也可以使用ObjectAnimator利用AnimatorSet來(lái)實(shí)現(xiàn)一個(gè)View上的多個(gè)同時(shí)進(jìn)行的動(dòng)畫效果,例如我們可以將

ViewPropertyAnimator.animate(mDownView) 
    .translationX(dismissRight ? mViewWidth : -mViewWidth)//X軸方向的移動(dòng)距離 
    .alpha(0) 
    .setDuration(mAnimationTime) 
    .setListener(new AnimatorListenerAdapter() { 
      @Override 
      public void onAnimationEnd(Animator animation) { 
        //Item滑出界面之后執(zhí)行刪除 
        performDismiss(mDownView, mDownPosition); 
      } 
    }); 

替換成

AnimatorSet set = new AnimatorSet(); 
      set.playTogether(ObjectAnimator.ofFloat(mDownView, "translationX", dismissRight ? mViewWidth : -mViewWidth),  
              ObjectAnimator.ofFloat(mDownView, "alpha", 0)); 
      set.setDuration(mAnimationTime).start(); 
      set.addListener(new AnimatorListenerAdapter() { 
            @Override 
            public void onAnimationEnd(Animator animation) { 
              //Item滑出界面之后執(zhí)行刪除 
              performDismiss(mDownView, mDownPosition); 
            } 
          }); 

在效果上面是一樣的,但是ViewPropertyAnimator在性能上要比使用ObjectAnimator來(lái)實(shí)現(xiàn)多個(gè)同時(shí)進(jìn)行的動(dòng)畫要高的多,舉個(gè)例子,假如要對(duì)View使用移動(dòng)和透明度的動(dòng)畫,使用ViewPropertyAnimator的話,某個(gè)時(shí)間點(diǎn)上我們只需要調(diào)用一次invalidate()方法刷新界面就行了,而使用ObjectAnimator的話,移動(dòng)的動(dòng)畫需要調(diào)用invalidate(),透明度的動(dòng)畫也需要調(diào)用invalidate()方法,在性能上使用AnimationSet比ViewPropertyAnimator要低,但是有的時(shí)候我們還是需要使用ObjectAnimator,比如,在某個(gè)時(shí)間內(nèi),我們需要將View先變大在變小在變大等復(fù)雜情況,這時(shí)候ObjectAnimator就派上用場(chǎng)了,例如

ObjectAnimator.ofInt(mDownView, "scaleX", 0 ,100 ,0, 100).setDuration(100).start() 
通過(guò)上面的幾步我們就實(shí)現(xiàn)了ListView的左右滑動(dòng)刪除item的效果啦,但是還有一個(gè)效果,item刪除之后,ListView的其他item向上或者向下緩緩滑動(dòng)的效果,實(shí)現(xiàn)這個(gè)也很容易,就是動(dòng)態(tài)設(shè)置item的高度,item高度逐漸變小,這樣其他的item就會(huì)出現(xiàn)向上或者向下擠壓的效果啦!

4. 這里我們使用的是ValueAnimator這個(gè)類,這個(gè)類并不是針對(duì)View作用的動(dòng)畫,而是對(duì)某個(gè)值作用的動(dòng)畫,他默認(rèn)使用的Interpolator(插補(bǔ)器)是AccelerateDecelerateInterpolator(開(kāi)始和結(jié)束的時(shí)候慢,中間快) , 舉個(gè)很簡(jiǎn)單的例子,我們?cè)?0秒內(nèi)使用ValueAnimator將某個(gè)值從0變化到100,如果使用LinearInterpolator(線性插補(bǔ)器,勻速變化)在第2秒的時(shí)候,這個(gè)值變成了20,而是用AccelerateDecelerateInterpolator,可能在第二秒的時(shí)候這個(gè)值為15或者13,所以我們?cè)赩alueAnimator變化的時(shí)候設(shè)置值動(dòng)畫變化的監(jiān)聽(tīng)器AnimatorUpdateListener就知道某個(gè)時(shí)間這個(gè)值變成了多少,從而對(duì)View的某個(gè)屬性進(jìn)行設(shè)置(例如大?。?,所以ValueAnimator是間接的對(duì)View設(shè)置動(dòng)畫的
了解了ValueAnimator的使用原理,我們就可以現(xiàn)實(shí)上面的動(dòng)畫效果了,我們使用ValueAnimator將item的高度變成0,設(shè)置ValueAnimator變化的監(jiān)聽(tīng),我們?cè)诨卣{(diào)函數(shù)onAnimationUpdate()中動(dòng)態(tài)的設(shè)置item的高度, 然后添加AnimatorListener監(jiān)聽(tīng)動(dòng)畫的狀態(tài)(例如動(dòng)畫開(kāi)始,結(jié)束,重復(fù)等)監(jiān)聽(tīng),在動(dòng)畫結(jié)束的回調(diào)函數(shù)onAnimationEnd()中刪除該item的數(shù)據(jù),調(diào)用notifyDataSetChanged刷新ListView,看看下面這段代碼

ViewHelper.setAlpha(dismissView, 1f); 
        ViewHelper.setTranslationX(dismissView, 0); 
        ViewGroup.LayoutParams lp = dismissView.getLayoutParams(); 
        lp.height = originalHeight; 
        dismissView.setLayoutParams(lp); 

我們使用動(dòng)畫只是將item移動(dòng)出了屏幕,并且將item的高度設(shè)置為了0,并沒(méi)有將item的View從ListView中Remove掉,況且ListView也不能直接Remove掉Item的,只能將數(shù)據(jù)源刪除,在調(diào)用notifyDataSetChanged()刷新,所以我們需要將剛剛滑出屏幕高度設(shè)置為0的Item恢復(fù)回來(lái)

自定義控件的代碼我們已經(jīng)編寫完了,接下來(lái)我們就要使用它了,先看界面的布局代碼

<RelativeLayout 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">  
  
  <com.example.swipedismisslistview.SwipeDismissListView 
    android:id="@+id/swipeDismissListView"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:listSelector="@android:color/transparent" 
    android:cacheColorHint="@android:color/transparent">  
  </com.example.swipedismisslistview.SwipeDismissListView> 
  
</RelativeLayout>  

很簡(jiǎn)單,一個(gè)RelativeLayout包裹我們自定義的ListView控件,接下來(lái)就是主界面的代碼編寫,跟平常的ListView使用一樣,但是我們需要設(shè)置OnDismissCallback()監(jiān)聽(tīng),在
onDismiss()中刪除該位置對(duì)于的數(shù)據(jù),刷新ListView

package com.example.swipedismisslistview; 
 
import java.util.ArrayList; 
import java.util.List; 
 
import android.app.Activity; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.AdapterView; 
import android.widget.AdapterView.OnItemClickListener; 
import android.widget.ArrayAdapter; 
import android.widget.Toast; 
 
import com.example.swipedismisslistview.SwipeDismissListView.OnDismissCallback; 
 
public class SwipeActivity extends Activity { 
  private SwipeDismissListView swipeDismissListView; 
  private ArrayAdapter<String> adapter; 
  private List<String> dataSourceList = new ArrayList<String>(); 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_swipe); 
    init(); 
  } 
 
  private void init() { 
    swipeDismissListView = (SwipeDismissListView) findViewById(R.id.swipeDismissListView); 
    for (int i = 0; i < 20; i++) { 
      dataSourceList.add("滑動(dòng)刪除" + i); 
    } 
 
    adapter = new ArrayAdapter<String>(this, 
        android.R.layout.simple_list_item_1, 
        android.R.id.text1, dataSourceList); 
     
    swipeDismissListView.setAdapter(adapter); 
     
    swipeDismissListView.setOnDismissCallback(new OnDismissCallback() { 
       
      @Override 
      public void onDismiss(int dismissPosition) { 
         adapter.remove(adapter.getItem(dismissPosition));  
      } 
    }); 
     
     
    swipeDismissListView.setOnItemClickListener(new OnItemClickListener() { 
 
      @Override 
      public void onItemClick(AdapterView<?> parent, View view, 
          int position, long id) { 
        Toast.makeText(SwipeActivity.this, adapter.getItem(position), Toast.LENGTH_SHORT).show(); 
      } 
    }); 
 
  } 
 
} 

所有的代碼都已經(jīng)編寫完畢了,接下來(lái)就是運(yùn)行工程,看看具體的效果是不是我們想要的

201645144001733.gif (411×698)

相關(guān)文章

最新評(píng)論