http://blog.csdn.net/xiaanming/article/details/17539199
先說下實作該效果的主要思路
先根據手指觸摸的點來擷取點選的是listview的哪一個item
手指在螢幕中滑動我們利用scrollby()來使該item跟随手指一起滑動
手指放開的時候,我們判斷手指拖動的距離來判斷item到底是滑出螢幕還是回到開始位置
主要思路就是上面這三步,接下來我們就用代碼來實作吧,首先我們建立一個項目,叫slidecutlistview
根據需求我們需要自己自定義一個listview來實作該功能,接下來先貼出代碼再講解具體的實作
[java] view
plaincopy
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;
/**
* @blog http://blog.csdn.net/xiaanming
*
* @author xiaanming
*/
public class slidecutlistview extends listview {
/**
* 目前滑動的listview position
*/
private int slideposition;
* 手指按下x的坐标
private int downy;
* 手指按下y的坐标
private int downx;
* 螢幕寬度
private int screenwidth;
* listview的item
private view itemview;
* 滑動類
private scroller scroller;
private static final int snap_velocity = 600;
* 速度追蹤對象
private velocitytracker velocitytracker;
* 是否響應滑動,預設為不響應
private boolean isslide = false;
* 認為是使用者滑動的最小距離
private int mtouchslop;
* 移除item後的回調接口
private removelistener mremovelistener;
* 用來訓示item滑出螢幕的方向,向左或者向右,用一個枚舉值來标記
private removedirection removedirection;
// 滑動删除方向的枚舉值
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();
* 設定滑動删除的回調接口
* @param removelistener
public void setremovelistener(removelistener removelistener) {
this.mremovelistener = removelistener;
* 分發事件,主要做的是判斷點選的是那個item, 以及通過postdelayed來設定響應左右滑動事件
@override
public boolean dispatchtouchevent(motionevent event) {
switch (event.getaction()) {
case motionevent.action_down: {
addvelocitytracker(event);
// 假如scroller滾動還沒有結束,我們直接傳回
if (!scroller.isfinished()) {
return super.dispatchtouchevent(event);
}
downx = (int) event.getx();
downy = (int) event.gety();
slideposition = pointtoposition(downx, downy);
// 無效的position, 不做任何處理
if (slideposition == adapterview.invalid_position) {
// 擷取我們點選的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;
case motionevent.action_up:
recyclevelocitytracker();
return super.dispatchtouchevent(event);
* 往右滑動,getscrollx()傳回的是左邊緣的距離,就是以view左邊緣為原點到開始滑動的距離,是以向右邊滑動為負值
private void scrollright() {
removedirection = removedirection.right;
final int delta = (screenwidth + itemview.getscrollx());
// 調用startscroll方法來設定一些滾動的參數,我們在computescroll()方法中調用scrollto來滾動item
scroller.startscroll(itemview.getscrollx(), 0, -delta, 0,
math.abs(delta));
postinvalidate(); // 重新整理itemview
* 向左滑動,根據上面我們知道向左滑動為正值
private void scrollleft() {
removedirection = removedirection.left;
final int delta = (screenwidth - itemview.getscrollx());
scroller.startscroll(itemview.getscrollx(), 0, delta, 0,
* 根據手指滾動itemview的距離來判斷是滾動到開始位置還是向左或者向右滾動
private void scrollbydistancex() {
// 如果向左滾動的距離大于螢幕的三分之一,就讓其删除
if (itemview.getscrollx() >= screenwidth / 3) {
scrollleft();
} else if (itemview.getscrollx() <= -screenwidth / 3) {
scrollright();
} else {
// 滾回到原始位置,為了偷下懶這裡是直接調用scrollto滾動
itemview.scrollto(0, 0);
* 處理我們拖動listview item的邏輯
public boolean ontouchevent(motionevent ev) {
if (isslide && slideposition != adapterview.invalid_position) {
addvelocitytracker(ev);
final int action = ev.getaction();
int x = (int) ev.getx();
switch (action) {
case motionevent.action_move:
int deltax = downx - x;
downx = x;
// 手指拖動itemview滾動, deltax大于0向左滾動,小于0向右滾
itemview.scrollby(deltax, 0);
break;
case motionevent.action_up:
int velocityx = getscrollvelocity();
if (velocityx > snap_velocity) {
scrollright();
} else if (velocityx < -snap_velocity) {
scrollleft();
} else {
scrollbydistancex();
}
recyclevelocitytracker();
// 手指離開的時候就不響應左右滾動
isslide = false;
return true; // 拖動的時候listview不滾動
//否則直接交給listview來處理ontouchevent事件
return super.ontouchevent(ev);
public void computescroll() {
// 調用startscroll的時候scroller.computescrolloffset()傳回true,
if (scroller.computescrolloffset()) {
// 讓listview item根據目前的滾動偏移量進行滾動
itemview.scrollto(scroller.getcurrx(), scroller.getcurry());
postinvalidate();
// 滾動動畫結束的時候調用回調接口
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方向的滑動速度,大于0向右滑動,反之向左
* @return
private int getscrollvelocity() {
velocitytracker.computecurrentvelocity(1000);
int velocity = (int) velocitytracker.getxvelocity();
return velocity;
* 當listview item滑出螢幕,回調這個接口
* 我們需要在回調方法removeitem()中移除該item,然後重新整理listview
* @author xiaanming
*
public interface removelistener {
public void removeitem(removedirection direction, int position);
}
首先我們重寫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()裡面的這段代碼
itemview.scrollto(0, 0);
我們需要将該item滾動在(0, 0 )這個點,因為我們隻是将listview的item滾動出螢幕而已,并沒有将該item移除,而且我們不能手動調用removeview()來從listview中移除該item,我們隻能通過改變listview的資料,然後通過notifydatasetchanged()來重新整理listview,是以我們需要将其滾動至(0, 0),這裡比較關鍵。
定義好了我們左右滑動的listview,接下來就來使用它,布局很簡單,一個relativelayout包裹我們自定義的listview
[html] view
<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>
接下來我們來看看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>
還記得我在上一篇文章中提到過調用scrollto()方法是對裡面的子view進行滾動的,而不是對整個布局進行滾動的,是以我們用linearlayout來套住我們的item的布局,這點需要注意一下,不然滾動的隻是textview。
首頁面mainactivity裡面的代碼比較簡單,裡面使用的也是arrayadapter,相信大家都能看懂
import java.util.arraylist;
import java.util.list;
import android.app.activity;
import android.os.bundle;
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>();
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("滑動删除" + 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();
});
//滑動删除之後的回調方法
public void removeitem(removedirection direction, int position) {
adapter.remove(adapter.getitem(position));
switch (direction) {
case right:
toast.maketext(this, "向右删除 "+ position, toast.length_short).show();
case left:
toast.maketext(this, "向左删除 "+ position, toast.length_short).show();
default:
}
這裡面需要對slidecutlistview設定removelistener,然後我們在回調方法removeitem(removedirection direction, int position)中删除該position的資料,在調用notifydatasetchanged()重新整理listview,我這裡用的是arrayadatper,直接調用remove()就可以了。
所有的代碼就編寫完了,我們來運作下程式看看效果吧
好了,今天的講解就到此結束了,有疑問的朋友可以在下面留言,我會幫大家解答的。今天是2013年的最後一天,希望大家開開心心度過2013,也開開心心的迎接2014,提前祝大家元旦快樂!
項目源碼,點選下載下傳