列表左右滑动删除demo.
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="60dip"
android:gravity="center"
android:textColor="#ff000000"
android:background="#ff00ff00"
/>
</LinearLayout>
activity_main.xml
<LinearLayout 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:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world"
tools:context=".MainActivity" />
<com.android.listviewtest.TestListView
android:id="@+id/lv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:stackFromBottom="true"
android:fadingEdge="vertical"
android:scrollbars="none"
android:fadingEdgeLength="20dip"
android:layout_gravity="bottom|left"
android:clipToPadding="false"
android:clipChildren="false"
>
<LinearLayout
android:id="@+id/linear_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical" >
</LinearLayout>
</com.android.listviewtest.TestListView>
</LinearLayout>
SwipeHelper.java
package com.android.listviewtest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.RectF;
import android.util.Log;
import android.view.animation.LinearInterpolator;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
public class SwipeHelper {
static final String TAG = "com.android.systemui.SwipeHelper";
private static final boolean DEBUG = false;
private static final boolean DEBUG_INVALIDATE = false;
private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
private static final boolean CONSTRAIN_SWIPE = true;
private static final boolean FADE_OUT_DURING_SWIPE = true;
private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
public static final int X = 0;
public static final int Y = 1;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
// where fade starts
static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
// beyond which alpha->0
private float mPagingTouchSlop;
private Callback mCallback;
private int mSwipeDirection;
private VelocityTracker mVelocityTracker;
private float mInitialTouchPos;
private boolean mDragging;
private View mCurrView;
private View mCurrAnimView;
private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
float pagingTouchSlop) {
mCallback = callback;
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = densityScale;
mPagingTouchSlop = pagingTouchSlop;
}
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
}
private float getPos(MotionEvent ev) {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
private float getTranslation(View v) {
return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
}
private float getVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getXVelocity() :
vt.getYVelocity();
}
private ObjectAnimator createTranslationAnimation(View v, float newPos) {
ObjectAnimator anim = ObjectAnimator.ofFloat(v,
mSwipeDirection == X ? "translationX" : "translationY", newPos);
return anim;
}
private float getPerpendicularVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getYVelocity() :
vt.getXVelocity();
}
private void setTranslation(View v, float translate) {
if (mSwipeDirection == X) {
v.setTranslationX(translate);
} else {
v.setTranslationY(translate);
}
}
private float getSize(View v) {
return mSwipeDirection == X ? v.getMeasuredWidth() :
v.getMeasuredHeight();
}
private float getAlphaForOffset(View view) {
float viewSize = getSize(view);
final float fadeSize = ALPHA_FADE_END * viewSize;
float result = 1.0f;
float pos = getTranslation(view);
if (pos >= viewSize * ALPHA_FADE_START) {
result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
} else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
}
// Make .03 alpha the minimum so you always see the item a bit-- slightly below
// .03, the item disappears entirely (as if alpha = 0) and that discontinuity looks
// a bit jarring
return Math.max(0.03f, result);
}
// invalidate the view's own bounds all the way up the view hierarchy
public static void invalidateGlobalRegion(View view) {
invalidateGlobalRegion(
view,
new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
}
// invalidate a rectangle relative to the view's coordinate system all the way up the view
// hierarchy
public static void invalidateGlobalRegion(View view, RectF childBounds) {
//childBounds.offset(view.getTranslationX(), view.getTranslationY());
if (DEBUG_INVALIDATE)
Log.v(TAG, "-------------");
while (view.getParent() != null && view.getParent() instanceof View) {
view = (View) view.getParent();
view.getMatrix().mapRect(childBounds);
view.invalidate((int) Math.floor(childBounds.left),
(int) Math.floor(childBounds.top),
(int) Math.ceil(childBounds.right),
(int) Math.ceil(childBounds.bottom));
if (DEBUG_INVALIDATE) {
Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
+ "," + (int) Math.floor(childBounds.top)
+ "," + (int) Math.ceil(childBounds.right)
+ "," + (int) Math.ceil(childBounds.bottom));
}
}
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragging = false;
mCurrView = mCallback.getChildAtPosition(ev);
mVelocityTracker.clear();
if (mCurrView != null) {
mCurrAnimView = mCallback.getChildContentView(mCurrView);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
}
break;
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
float delta = pos - mInitialTouchPos;
if (Math.abs(delta) > mPagingTouchSlop) {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDragging = false;
mCurrView = null;
mCurrAnimView = null;
break;
}
return mDragging;
}
/**
* @param view The view to be dismissed
* @param velocity The desired pixels/second speed at which the view should move
*/
public void dismissChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
float newPos;
if (velocity < 0
|| (velocity == 0 && getTranslation(animView) < 0)
// if we use the Menu to dismiss an item in landscape, animate up
|| (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)) {
newPos = -getSize(animView);
} else {
newPos = getSize(animView);
}
int duration = MAX_ESCAPE_ANIMATION_DURATION;
if (velocity != 0) {
duration = Math.min(duration,
(int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
.abs(velocity)));
} else {
duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
}
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator anim = createTranslationAnimation(animView, newPos);
anim.setInterpolator(sLinearInterpolator);
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view);
animView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
animView.setAlpha(getAlphaForOffset(animView));
}
invalidateGlobalRegion(animView);
}
});
anim.start();
}
public void snapChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
ObjectAnimator anim = createTranslationAnimation(animView, 0);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
animView.setAlpha(getAlphaForOffset(animView));
}
invalidateGlobalRegion(animView);
}
});
anim.start();
}
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
float delta = getPos(ev) - mInitialTouchPos;
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
float size = getSize(mCurrAnimView);
float maxScrollDistance = 0.15f * size;
if (Math.abs(delta) >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
}
}
setTranslation(mCurrAnimView, delta);
if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
mCurrAnimView.setAlpha(getAlphaForOffset(mCurrAnimView));
}
invalidateGlobalRegion(mCurrView);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mCurrView != null) {
float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
float velocity = getVelocity(mVelocityTracker);
float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
// Decide whether to dismiss the current view
boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
Math.abs(getTranslation(mCurrAnimView)) > 0.4 * getSize(mCurrAnimView);
boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
(Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
(velocity > 0) == (getTranslation(mCurrAnimView) > 0);
boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) &&
(childSwipedFastEnough || childSwipedFarEnough);
if (dismissChild) {
// flingadingy
dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
} else {
// snappity
mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, velocity);
}
}
break;
}
return true;
}
public interface Callback {
View getChildAtPosition(MotionEvent ev);
View getChildContentView(View v);
boolean canChildBeDismissed(View v);
void onBeginDrag(View v);
void onChildDismissed(View v);
void onDragCancelled(View v);
}
}
TestListView.java
package com.android.listviewtest;
import android.animation.LayoutTransition;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.SimpleAdapter;
public class TestListView extends ScrollView implements SwipeHelper.Callback {
private SimpleAdapter mAdapter;
private SwipeHelper mSwipeHelper;
private LinearLayout mLinearLayout;
private Context mContext;
public TestListView(Context mContext, AttributeSet attrs) {
super(mContext, attrs, 0);
this.mContext = mContext;
float densityScale = getResources().getDisplayMetrics().density;
float pagingTouchSlop = ViewConfiguration.get(mContext)
.getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale,
pagingTouchSlop);
}
public void setAdapter(SimpleAdapter Adapter) {
mAdapter = Adapter;
mAdapter.registerDataSetObserver(new DataSetObserver() {
public void onChanged() {
update();
}
public void onInvalidated() {
update();
}
});
update();
}
@Override
public void removeViewInLayout(final View view) {
dismissChild(view);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mSwipeHelper.onInterceptTouchEvent(ev)
|| super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
}
public void dismissChild(View v) {
mSwipeHelper.dismissChild(v, 0);
}
@Override
public View getChildAtPosition(MotionEvent ev) {
final float x = ev.getX() + getScrollX();
final float y = ev.getY() + getScrollY();
for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
View item = mLinearLayout.getChildAt(i);
if (item.getVisibility() == View.VISIBLE && x >= item.getLeft()
&& x < item.getRight() && y >= item.getTop()
&& y < item.getBottom()) {
return item;
}
}
return null;
}
@Override
public View getChildContentView(View v) {
return v.findViewById(R.id.text1);
}
@Override
public boolean canChildBeDismissed(View v) {
return true;
}
@Override
public void onBeginDrag(View v) {
requestDisallowInterceptTouchEvent(true);
v.setActivated(true);
}
@Override
public void onChildDismissed(View v) {
mLinearLayout.removeView(v);
}
@Override
public void onDragCancelled(View v) {
v.setActivated(false);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setScrollbarFadingEnabled(true);
mLinearLayout = (LinearLayout) findViewById(R.id.linear_layout);
}
private void update()
{
mLinearLayout.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
View old = null;
if (i < mLinearLayout.getChildCount()) {
old = mLinearLayout.getChildAt(i);
old.setVisibility(View.VISIBLE);
}
final View view = mAdapter.getView(i, old, mLinearLayout);
if (old == null) {
OnTouchListener noOpListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
};
view.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
}
});
// We don't want a click sound when we dimiss recents
view.setSoundEffectsEnabled(false);
OnClickListener launchAppListener = new OnClickListener() {
public void onClick(View v) {
}
};
OnLongClickListener longClickListener = new OnLongClickListener() {
public boolean onLongClick(View v) {
return true;
}
};
mLinearLayout.addView(view);
}
}
for (int i = mAdapter.getCount(); i < mLinearLayout.getChildCount(); i++) {
mLinearLayout.getChildAt(i).setVisibility(View.GONE);
}
}
}
MainActivity.java
package com.android.listviewtest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.os.Bundle; import android.app.Activity; import android.widget.ListView; import android.widget.ScrollView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { private TestListView mScrollView; private SimpleAdapter mAdapter; private List<Map<String, Object>> list = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mScrollView = (TestListView) findViewById(R.id.lv); list = new ArrayList<Map<String, Object>>(); for (int n = 0; n < 10; n++) { Map<String, Object> hashmap = new HashMap<String, Object>(); hashmap.put("item", "" + n); list.add(hashmap); } mAdapter = new SimpleAdapter(MainActivity.this, list, R.layout.list_item, new String[] { "item" }, new int[] { R.id.text1 }); mScrollView.setAdapter(mAdapter); } }