列表左右滑动删除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); } }