《Android SwipeToDismiss:左右滑动删除ListView条目Item》
Android的SwipeToDismiss是github上一个第三方开源框架(github上的项目链接地址:https://github.com/romannurik/Android-SwipeToDismiss )。该开源项目旨在:使得一个ListView的item在用户的手指在屏幕上左滑或者右滑时候,删除当前的这个ListView Item。
1 package com.lixu.SwipeRefreshLayoutyongfa; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 6 import com.lixu.SwipeRefreshLayoutyongfa.SwipeDismissListViewTouchListener.DismissCallbacks; 7 import android.app.Activity; 8 import android.content.Context; 9 import android.os.AsyncTask; 10 import android.os.Bundle; 11 import android.support.v4.widget.SwipeRefreshLayout; 12 import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; 13 import android.view.LayoutInflater; 14 import android.view.View; 15 import android.view.ViewGroup; 16 import android.widget.ArrayAdapter; 17 import android.widget.ImageView; 18 import android.widget.ListView; 19 import android.widget.TextView; 20 21 public class MainActivity extends Activity { 22 private ArrayAdapter<String> adapter; 23 private ArrayList<HashMap<String, Object>> data; 24 private int count = 0; 25 SwipeRefreshLayout srl; 26 private int[] image = { R.drawable.beijing, R.drawable.chengdu, R.drawable.guangzhou, R.drawable.hangzhou, 27 R.drawable.wuhan, R.drawable.xian, R.drawable.shenzhen }; 28 29 private String[] city = { "北京", "成都", "广州", "杭州", "武汉", "西安", "深圳" }; 30 private String KEY_IMAGE = "image"; 31 private String KEY_TXT = "txt"; 32 33 private int i = 0; 34 private int j = 0; 35 36 ListView lv; 37 38 SwipeDismissListViewTouchListener touchlistener; 39 40 @Override 41 protected void onCreate(Bundle savedInstanceState) { 42 super.onCreate(savedInstanceState); 43 setContentView(R.layout.activity_main); 44 45 data = new ArrayList<HashMap<String, Object>>(); 46 47 lv = (ListView) findViewById(R.id.lv); 48 49 srl = (SwipeRefreshLayout) findViewById(R.id.srl); 50 // 设置刷新动画的颜色. 51 srl.setColorSchemeResources(android.R.color.holo_green_light, android.R.color.holo_blue_bright, 52 android.R.color.holo_red_light); 53 54 srl.setOnRefreshListener(new OnRefreshListener() { 55 // SwipeRefreshLayout接管其包裹的ListView下拉事件。 56 // 每一次对ListView的下拉动作,将触发SwipeRefreshLayout的onRefresh()。 57 @SuppressWarnings("unchecked") 58 @Override 59 public void onRefresh() { 60 61 new MyAsyncTask().execute(); 62 63 } 64 }); 65 adapter = new MyAdapter(this, -1); 66 67 lv.setAdapter(adapter); 68 addSwipeDismissListView(); 69 // 往listview里面添加触摸事件 70 lv.setOnTouchListener(touchlistener); 71 } 72 73 private void addSwipeDismissListView() { 74 touchlistener = new SwipeDismissListViewTouchListener(lv, new DismissCallbacks() { 75 76 @Override 77 public void onDismiss(ListView listView, int[] reverseSortedPositions) { 78 for (int pos : reverseSortedPositions) 79 // 获取滑动的下标行后删除这一行 80 data.remove(pos); 81 // 每次要刷新适配器 82 adapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "删除成功!", 0).show(); 83 } 84 85 @Override 86 public boolean canDismiss(int position) { 87 return true; 88 } 89 }); 90 } 91 92 private class MyAdapter extends ArrayAdapter { 93 private LayoutInflater flater; 94 95 public MyAdapter(Context context, int resource) { 96 super(context, resource); 97 flater = LayoutInflater.from(context); 98 } 99 100 @Override101 public int getCount() {102 return data.size();103 }104 105 @Override106 public View getView(int position, View convertView, ViewGroup parent) {107 if (convertView == null)108 convertView = flater.inflate(R.layout.item, null);109 110 TextView tv = (TextView) convertView.findViewById(R.id.tv);111 tv.setText(data.get(position).get(KEY_TXT) + "");112 113 ImageView iv = (ImageView) convertView.findViewById(R.id.iv);114 iv.setImageResource((Integer) data.get(position).get(KEY_IMAGE));115 116 return convertView;117 }118 }119 120 @SuppressWarnings("rawtypes")121 private class MyAsyncTask extends AsyncTask {122 private HashMap<String, Object> map;123 124 @Override125 protected void onPreExecute() {126 // 刷新开始127 srl.setRefreshing(true);128 map = new HashMap<String, Object>();129 }130 131 @Override132 protected Object doInBackground(Object... params) {133 // 防止下标越界,到达数组长度后返回开始134 if (i == image.length)135 i = 0;136 if (j == city.length)137 j = 0;138 139 map.put(KEY_IMAGE, image[i++]);140 map.put(KEY_TXT, city[j++]);141 142 // 处理一些耗时的事件143 return map;144 }145 146 @SuppressWarnings("unchecked")147 @Override148 protected void onPostExecute(Object result) {149 // 每次从头部添加150 data.add(0, (HashMap<String, Object>) result);151 // 刷新适配器152 adapter.notifyDataSetChanged();153 // 刷新完毕154 srl.setRefreshing(false);155 }156 157 }158 159 }
需要用的SwipeDismissListViewTouchListener类
1 package com.lixu.SwipeRefreshLayoutyongfa; 2 3 /* 4 * Copyright 2013 Google Inc. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.graphics.Rect; 23 import android.os.SystemClock; 24 import android.view.MotionEvent; 25 import android.view.VelocityTracker; 26 import android.view.View; 27 import android.view.ViewConfiguration; 28 import android.view.ViewGroup; 29 import android.view.ViewPropertyAnimator; 30 import android.widget.AbsListView; 31 import android.widget.ListView; 32 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.List; 36 37 /** 38 * A {@link View.OnTouchListener} that makes the list items in a 39 * {@link ListView} dismissable. {@link ListView} is given special treatment 40 * because by default it handles touches for its list items... i.e. it's in 41 * charge of drawing the pressed state (the list selector), handling list item 42 * clicks, etc. 43 * 44 * <p> 45 * After creating the listener, the caller should also call 46 * {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, passing 47 * in the scroll listener returned by {@link #makeScrollListener()}. If a scroll 48 * listener is already assigned, the caller should still pass scroll changes 49 * through to this listener. This will ensure that this 50 * {@link SwipeDismissListViewTouchListener} is paused during list view 51 * scrolling. 52 * </p> 53 * 54 * <p> 55 * Example usage: 56 * </p> 57 * 58 * <pre> 59 * SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener( 60 * listView, new SwipeDismissListViewTouchListener.OnDismissCallback() { 61 * public void onDismiss(ListView listView, 62 * int[] reverseSortedPositions) { 63 * for (int position : reverseSortedPositions) { 64 * adapter.remove(adapter.getItem(position)); 65 * } 66 * adapter.notifyDataSetChanged(); 67 * } 68 * }); 69 * listView.setOnTouchListener(touchListener); 70 * listView.setOnScrollListener(touchListener.makeScrollListener()); 71 * </pre> 72 * 73 * <p> 74 * This class Requires API level 12 or later due to use of 75 * {@link ViewPropertyAnimator}. 76 * </p> 77 * 78 * <p> 79 * For a generalized {@link View.OnTouchListener} that makes any view 80 * dismissable, see {@link SwipeDismissTouchListener}. 81 * </p> 82 * 83 * @see SwipeDismissTouchListener 84 */ 85 public class SwipeDismissListViewTouchListener implements View.OnTouchListener { 86 // Cached ViewConfiguration and system-wide constant values 87 private int mSlop; 88 private int mMinFlingVelocity; 89 private int mMaxFlingVelocity; 90 private long mAnimationTime; 91 92 // Fixed properties 93 private ListView mListView; 94 private DismissCallbacks mCallbacks; 95 private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero 96 97 // Transient properties 98 private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>(); 99 private int mDismissAnimationRefCount = 0;100 private float mDownX;101 private float mDownY;102 private boolean mSwiping;103 private int mSwipingSlop;104 private VelocityTracker mVelocityTracker;105 private int mDownPosition;106 private View mDownView;107 private boolean mPaused;108 109 /**110 * The callback interface used by {@link SwipeDismissListViewTouchListener}111 * to inform its client about a successful dismissal of one or more list112 * item positions.113 */114 public interface DismissCallbacks {115 /**116 * Called to determine whether the given position can be dismissed.117 */118 boolean canDismiss(int position);119 120 /**121 * Called when the user has indicated they she would like to dismiss one122 * or more list item positions.123 *124 * @param listView125 * The originating {@link ListView}.126 * @param reverseSortedPositions127 * An array of positions to dismiss, sorted in descending128 * order for convenience.129 */130 void onDismiss(ListView listView, int[] reverseSortedPositions);131 }132 133 /**134 * Constructs a new swipe-to-dismiss touch listener for the given list view.135 *136 * @param listView137 * The list view whose items should be dismissable.138 * @param callbacks139 * The callback to trigger when the user has indicated that she140 * would like to dismiss one or more list items.141 */142 public SwipeDismissListViewTouchListener(ListView listView,143 DismissCallbacks callbacks) {144 ViewConfiguration vc = ViewConfiguration.get(listView.getContext());145 mSlop = vc.getScaledTouchSlop();146 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;147 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();148 mAnimationTime = listView.getContext().getResources()149 .getInteger(android.R.integer.config_shortAnimTime);150 mListView = listView;151 mCallbacks = callbacks;152 }153 154 /**155 * Enables or disables (pauses or resumes) watching for swipe-to-dismiss156 * gestures.157 *158 * @param enabled159 * Whether or not to watch for gestures.160 */161 public void setEnabled(boolean enabled) {162 mPaused = !enabled;163 }164 165 /**166 * Returns an {@link AbsListView.OnScrollListener} to be added to the167 * {@link ListView} using168 * {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}. If a169 * scroll listener is already assigned, the caller should still pass scroll170 * changes through to this listener. This will ensure that this171 * {@link SwipeDismissListViewTouchListener} is paused during list view172 * scrolling.</p>173 *174 * @see SwipeDismissListViewTouchListener175 */176 public AbsListView.OnScrollListener makeScrollListener() {177 return new AbsListView.OnScrollListener() {178 @Override179 public void onScrollStateChanged(AbsListView absListView,180 int scrollState) {181 setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);182 }183 184 @Override185 public void onScroll(AbsListView absListView, int i, int i1, int i2) {186 }187 };188 }189 190 @Override191 public boolean onTouch(View view, MotionEvent motionEvent) {192 if (mViewWidth < 2) {193 mViewWidth = mListView.getWidth();194 }195 196 switch (motionEvent.getActionMasked()) {197 case MotionEvent.ACTION_DOWN: {198 if (mPaused) {199 return false;200 }201 202 // TODO: ensure this is a finger, and set a flag203 204 // Find the child view that was touched (perform a hit test)205 Rect rect = new Rect();206 int childCount = mListView.getChildCount();207 int[] listViewCoords = new int[2];208 mListView.getLocationOnScreen(listViewCoords);209 int x = (int) motionEvent.getRawX() - listViewCoords[0];210 int y = (int) motionEvent.getRawY() - listViewCoords[1];211 View child;212 for (int i = 0; i < childCount; i++) {213 child = mListView.getChildAt(i);214 child.getHitRect(rect);215 if (rect.contains(x, y)) {216 mDownView = child;217 break;218 }219 }220 221 if (mDownView != null) {222 mDownX = motionEvent.getRawX();223 mDownY = motionEvent.getRawY();224 mDownPosition = mListView.getPositionForView(mDownView);225 if (mCallbacks.canDismiss(mDownPosition)) {226 mVelocityTracker = VelocityTracker.obtain();227 mVelocityTracker.addMovement(motionEvent);228 } else {229 mDownView = null;230 }231 }232 return false;233 }234 235 case MotionEvent.ACTION_CANCEL: {236 if (mVelocityTracker == null) {237 break;238 }239 240 if (mDownView != null && mSwiping) {241 // cancel242 mDownView.animate().translationX(0).alpha(1)243 .setDuration(mAnimationTime).setListener(null);244 }245 mVelocityTracker.recycle();246 mVelocityTracker = null;247 mDownX = 0;248 mDownY = 0;249 mDownView = null;250 mDownPosition = ListView.INVALID_POSITION;251 mSwiping = false;252 break;253 }254 255 case MotionEvent.ACTION_UP: {256 if (mVelocityTracker == null) {257 break;258 }259 260 float deltaX = motionEvent.getRawX() - mDownX;261 mVelocityTracker.addMovement(motionEvent);262 mVelocityTracker.computeCurrentVelocity(1000);263 float velocityX = mVelocityTracker.getXVelocity();264 float absVelocityX = Math.abs(velocityX);265 float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());266 boolean dismiss = false;267 boolean dismissRight = false;268 if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {269 dismiss = true;270 dismissRight = deltaX > 0;271 } else if (mMinFlingVelocity <= absVelocityX272 && absVelocityX <= mMaxFlingVelocity273 && absVelocityY < absVelocityX && mSwiping) {274 // dismiss only if flinging in the same direction as dragging275 dismiss = (velocityX < 0) == (deltaX < 0);276 dismissRight = mVelocityTracker.getXVelocity() > 0;277 }278 if (dismiss && mDownPosition != ListView.INVALID_POSITION) {279 // dismiss280 final View downView = mDownView; // mDownView gets null'd before281 // animation ends282 final int downPosition = mDownPosition;283 ++mDismissAnimationRefCount;284 mDownView.animate()285 .translationX(dismissRight ? mViewWidth : -mViewWidth)286 .alpha(0).setDuration(mAnimationTime)287 .setListener(new AnimatorListenerAdapter() {288 @Override289 public void onAnimationEnd(Animator animation) {290 performDismiss(downView, downPosition);291 }292 });293 } else {294 // cancel295 mDownView.animate().translationX(0).alpha(1)296 .setDuration(mAnimationTime).setListener(null);297 }298 mVelocityTracker.recycle();299 mVelocityTracker = null;300 mDownX = 0;301 mDownY = 0;302 mDownView = null;303 mDownPosition = ListView.INVALID_POSITION;304 mSwiping = false;305 break;306 }307 308 case MotionEvent.ACTION_MOVE: {309 if (mVelocityTracker == null || mPaused) {310 break;311 }312 313 mVelocityTracker.addMovement(motionEvent);314 float deltaX = motionEvent.getRawX() - mDownX;315 float deltaY = motionEvent.getRawY() - mDownY;316 if (Math.abs(deltaX) > mSlop317 && Math.abs(deltaY) < Math.abs(deltaX) / 2) {318 mSwiping = true;319 mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);320 mListView.requestDisallowInterceptTouchEvent(true);321 322 // Cancel ListView's touch (un-highlighting the item)323 MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);324 cancelEvent325 .setAction(MotionEvent.ACTION_CANCEL326 | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));327 mListView.onTouchEvent(cancelEvent);328 cancelEvent.recycle();329 }330 331 if (mSwiping) {332 mDownView.setTranslationX(deltaX - mSwipingSlop);333 mDownView.setAlpha(Math.max(0f,334 Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));335 return true;336 }337 break;338 }339 }340 return false;341 }342 343 class PendingDismissData implements Comparable<PendingDismissData> {344 public int position;345 public View view;346 347 public PendingDismissData(int position, View view) {348 this.position = position;349 this.view = view;350 }351 352 @Override353 public int compareTo(PendingDismissData other) {354 // Sort by descending position355 return other.position - position;356 }357 }358 359 private void performDismiss(final View dismissView,360 final int dismissPosition) {361 // Animate the dismissed list item to zero-height and fire the dismiss362 // callback when363 // all dismissed list item animations have completed. This triggers364 // layout on each animation365 // frame; in the future we may want to do something smarter and more366 // performant.367 368 final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();369 final int originalHeight = dismissView.getHeight();370 371 ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1)372 .setDuration(mAnimationTime);373 374 animator.addListener(new AnimatorListenerAdapter() {375 @Override376 public void onAnimationEnd(Animator animation) {377 --mDismissAnimationRefCount;378 if (mDismissAnimationRefCount == 0) {379 // No active animations, process all pending dismisses.380 // Sort by descending position381 Collections.sort(mPendingDismisses);382 383 int[] dismissPositions = new int[mPendingDismisses.size()];384 for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {385 dismissPositions[i] = mPendingDismisses.get(i).position;386 }387 mCallbacks.onDismiss(mListView, dismissPositions);388 389 // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying390 // to start a dismiss391 // animation with a stale position392 mDownPosition = ListView.INVALID_POSITION;393 394 ViewGroup.LayoutParams lp;395 for (PendingDismissData pendingDismiss : mPendingDismisses) {396 // Reset view presentation397 pendingDismiss.view.setAlpha(1f);398 pendingDismiss.view.setTranslationX(0);399 lp = pendingDismiss.view.getLayoutParams();400 lp.height = originalHeight;401 pendingDismiss.view.setLayoutParams(lp);402 }403 404 // Send a cancel event405 long time = SystemClock.uptimeMillis();406 MotionEvent cancelEvent = MotionEvent.obtain(time, time,407 MotionEvent.ACTION_CANCEL, 0, 0, 0);408 mListView.dispatchTouchEvent(cancelEvent);409 410 mPendingDismisses.clear();411 }412 }413 });414 415 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {416 @Override417 public void onAnimationUpdate(ValueAnimator valueAnimator) {418 lp.height = (Integer) valueAnimator.getAnimatedValue();419 dismissView.setLayoutParams(lp);420 }421 });422 423 mPendingDismisses.add(new PendingDismissData(dismissPosition,424 dismissView));425 animator.start();426 }427 }
xml文件:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 tools:context="com.lixu.SwipeRefreshLayoutyongfa.MainActivity" > 6 7 <android.support.v4.widget.SwipeRefreshLayout 8 android:id="@+id/srl" 9 android:layout_width="match_parent"10 android:layout_height="match_parent" >11 12 <ListView13 android:id="@+id/lv"14 android:layout_width="match_parent"15 android:layout_height="match_parent" />16 </android.support.v4.widget.SwipeRefreshLayout>17 18 </RelativeLayout>
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <ImageView 8 android:id="@+id/iv" 9 android:layout_width="50dp"10 android:layout_height="50dp" />11 12 <TextView13 android:id="@+id/tv"14 android:layout_width="wrap_content"15 android:layout_height="wrap_content"16 android:textSize="25sp" />17 18 </LinearLayout>
运行效果图: