《Android SwipeToDismiss:左右滑动删除ListView条目Item》
Android的SwipeToDismiss是github上一个第三方开源框架(github上的项目链接地址:https://github.com/romannurik/Android-SwipeToDismiss )。该开源项目旨在:使得一个ListView的item在用户的手指在屏幕上左滑或者右滑时候,删除当前的这个ListView Item。
package com.lixu.SwipeRefreshLayoutyongfa; import java.util.ArrayList;
import java.util.HashMap; import com.lixu.SwipeRefreshLayoutyongfa.SwipeDismissListViewTouchListener.DismissCallbacks;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; public class MainActivity extends Activity {
private ArrayAdapter<String> adapter;
private ArrayList<HashMap<String, Object>> data;
private int count = 0;
SwipeRefreshLayout srl;
private int[] image = { R.drawable.beijing, R.drawable.chengdu, R.drawable.guangzhou, R.drawable.hangzhou,
R.drawable.wuhan, R.drawable.xian, R.drawable.shenzhen }; private String[] city = { "北京", "成都", "广州", "杭州", "武汉", "西安", "深圳" };
private String KEY_IMAGE = "image";
private String KEY_TXT = "txt"; private int i = 0;
private int j = 0; ListView lv; SwipeDismissListViewTouchListener touchlistener; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); data = new ArrayList<HashMap<String, Object>>(); lv = (ListView) findViewById(R.id.lv); srl = (SwipeRefreshLayout) findViewById(R.id.srl);
// 设置刷新动画的颜色.
srl.setColorSchemeResources(android.R.color.holo_green_light, android.R.color.holo_blue_bright,
android.R.color.holo_red_light); srl.setOnRefreshListener(new OnRefreshListener() {
// SwipeRefreshLayout接管其包裹的ListView下拉事件。
// 每一次对ListView的下拉动作,将触发SwipeRefreshLayout的onRefresh()。
@SuppressWarnings("unchecked")
@Override
public void onRefresh() { new MyAsyncTask().execute(); }
});
adapter = new MyAdapter(this, -1); lv.setAdapter(adapter);
addSwipeDismissListView();
// 往listview里面添加触摸事件
lv.setOnTouchListener(touchlistener);
} private void addSwipeDismissListView() {
touchlistener = new SwipeDismissListViewTouchListener(lv, new DismissCallbacks() { @Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
for (int pos : reverseSortedPositions)
// 获取滑动的下标行后删除这一行
data.remove(pos);
// 每次要刷新适配器
adapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "删除成功!", 0).show();
} @Override
public boolean canDismiss(int position) {
return true;
}
});
} private class MyAdapter extends ArrayAdapter {
private LayoutInflater flater; public MyAdapter(Context context, int resource) {
super(context, resource);
flater = LayoutInflater.from(context);
} @Override
public int getCount() {
return data.size();
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = flater.inflate(R.layout.item, null); TextView tv = (TextView) convertView.findViewById(R.id.tv);
tv.setText(data.get(position).get(KEY_TXT) + ""); ImageView iv = (ImageView) convertView.findViewById(R.id.iv);
iv.setImageResource((Integer) data.get(position).get(KEY_IMAGE)); return convertView;
}
} @SuppressWarnings("rawtypes")
private class MyAsyncTask extends AsyncTask {
private HashMap<String, Object> map; @Override
protected void onPreExecute() {
// 刷新开始
srl.setRefreshing(true);
map = new HashMap<String, Object>();
} @Override
protected Object doInBackground(Object... params) {
// 防止下标越界,到达数组长度后返回开始
if (i == image.length)
i = 0;
if (j == city.length)
j = 0; map.put(KEY_IMAGE, image[i++]);
map.put(KEY_TXT, city[j++]); // 处理一些耗时的事件
return map;
} @SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
// 每次从头部添加
data.add(0, (HashMap<String, Object>) result);
// 刷新适配器
adapter.notifyDataSetChanged();
// 刷新完毕
srl.setRefreshing(false);
} } }
需要用的SwipeDismissListViewTouchListener类
package com.lixu.SwipeRefreshLayoutyongfa; /*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.AbsListView;
import android.widget.ListView; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; /**
* A {@link View.OnTouchListener} that makes the list items in a
* {@link ListView} dismissable. {@link ListView} is given special treatment
* because by default it handles touches for its list items... i.e. it's in
* charge of drawing the pressed state (the list selector), handling list item
* clicks, etc.
*
* <p>
* After creating the listener, the caller should also call
* {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, passing
* in the scroll listener returned by {@link #makeScrollListener()}. If a scroll
* listener is already assigned, the caller should still pass scroll changes
* through to this listener. This will ensure that this
* {@link SwipeDismissListViewTouchListener} is paused during list view
* scrolling.
* </p>
*
* <p>
* Example usage:
* </p>
*
* <pre>
* SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(
* listView, new SwipeDismissListViewTouchListener.OnDismissCallback() {
* public void onDismiss(ListView listView,
* int[] reverseSortedPositions) {
* for (int position : reverseSortedPositions) {
* adapter.remove(adapter.getItem(position));
* }
* adapter.notifyDataSetChanged();
* }
* });
* listView.setOnTouchListener(touchListener);
* listView.setOnScrollListener(touchListener.makeScrollListener());
* </pre>
*
* <p>
* This class Requires API level 12 or later due to use of
* {@link ViewPropertyAnimator}.
* </p>
*
* <p>
* For a generalized {@link View.OnTouchListener} that makes any view
* dismissable, see {@link SwipeDismissTouchListener}.
* </p>
*
* @see SwipeDismissTouchListener
*/
public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime; // Fixed properties
private ListView mListView;
private DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero // Transient properties
private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private float mDownY;
private boolean mSwiping;
private int mSwipingSlop;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused; /**
* The callback interface used by {@link SwipeDismissListViewTouchListener}
* to inform its client about a successful dismissal of one or more list
* item positions.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the given position can be dismissed.
*/
boolean canDismiss(int position); /**
* Called when the user has indicated they she would like to dismiss one
* or more list item positions.
*
* @param listView
* The originating {@link ListView}.
* @param reverseSortedPositions
* An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismiss(ListView listView, int[] reverseSortedPositions);
} /**
* Constructs a new swipe-to-dismiss touch listener for the given list view.
*
* @param listView
* The list view whose items should be dismissable.
* @param callbacks
* The callback to trigger when the user has indicated that she
* would like to dismiss one or more list items.
*/
public SwipeDismissListViewTouchListener(ListView listView,
DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources()
.getInteger(android.R.integer.config_shortAnimTime);
mListView = listView;
mCallbacks = callbacks;
} /**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss
* gestures.
*
* @param enabled
* Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
} /**
* Returns an {@link AbsListView.OnScrollListener} to be added to the
* {@link ListView} using
* {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}. If a
* scroll listener is already assigned, the caller should still pass scroll
* changes through to this listener. This will ensure that this
* {@link SwipeDismissListViewTouchListener} is paused during list view
* scrolling.</p>
*
* @see SwipeDismissListViewTouchListener
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView,
int scrollState) {
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
} @Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
}
};
} @Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
} switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
return false;
} // TODO: ensure this is a finger, and set a flag // Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
} if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mListView.getPositionForView(mDownView);
if (mCallbacks.canDismiss(mDownPosition)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
return false;
} case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
} if (mDownView != null && mSwiping) {
// cancel
mDownView.animate().translationX(0).alpha(1)
.setDuration(mAnimationTime).setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
} case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
} float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX
&& absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
// dismiss
final View downView = mDownView; // mDownView gets null'd before
// animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0).setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);
}
});
} else {
// cancel
mDownView.animate().translationX(0).alpha(1)
.setDuration(mAnimationTime).setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
} case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
} mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (Math.abs(deltaX) > mSlop
&& Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mListView.requestDisallowInterceptTouchEvent(true); // Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent
.setAction(MotionEvent.ACTION_CANCEL
| (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
} if (mSwiping) {
mDownView.setTranslationX(deltaX - mSwipingSlop);
mDownView.setAlpha(Math.max(0f,
Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
} class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view; public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
} @Override
public int compareTo(PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
} private void performDismiss(final View dismissView,
final int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss
// callback when
// all dismissed list item animations have completed. This triggers
// layout on each animation
// frame; in the future we may want to do something smarter and more
// performant. final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight(); ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1)
.setDuration(mAnimationTime); animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses); int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
mCallbacks.onDismiss(mListView, dismissPositions); // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying
// to start a dismiss
// animation with a stale position
mDownPosition = ListView.INVALID_POSITION; ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
} // Send a cancel event
long time = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
MotionEvent.ACTION_CANCEL, 0, 0, 0);
mListView.dispatchTouchEvent(cancelEvent); mPendingDismisses.clear();
}
}
}); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
}); mPendingDismisses.add(new PendingDismissData(dismissPosition,
dismissView));
animator.start();
}
}
xml文件:
<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"
tools:context="com.lixu.SwipeRefreshLayoutyongfa.MainActivity" > <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="match_parent" > <ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout> </RelativeLayout>
<?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="vertical" > <ImageView
android:id="@+id/iv"
android:layout_width="50dp"
android:layout_height="50dp" /> <TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" /> </LinearLayout>
运行效果图: