当前位置: 代码迷 >> Android >> Android ListView侧滑item,仿QQ剔除效果
  详细解决方案

Android ListView侧滑item,仿QQ剔除效果

热度:61   发布时间:2016-04-28 02:41:41.0
Android ListView侧滑item,仿QQ删除效果

尊重原创,转载请注明(http://blog.csdn.net/aoshiwenrou/article/details/42971193)

最近的项目需求有一条是要实现仿QQ的侧滑删除效果,网上搜到了很多,但是与预想的都不太一样,于是自己研究了一下,写了一个Demo,记录下来。


功能:

1.实现了仿QQ的Item侧滑效果

2.可根据item的长度计算侧滑范围

3.实现item条目点击监听与删除按钮监听

4.解决了删除按钮出现时ListView滑动错位问题(偷懒:滑动时item复位)

主要代码:

SlidePauseListView:

package com.jcking.slidepauselistview.view;import android.content.Context;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;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;public class SlidePauseListView 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;	/**	 * 最多可滑动的范围 	 */	private int mTouchLimit;	/**	 * 是否已经侧滑	 */	private boolean hasSlided = false;	public SlidePauseListView(Context context) {		this(context, null);	}	public SlidePauseListView(Context context, AttributeSet attrs) {		this(context, attrs, 0);	}	public SlidePauseListView(Context context, AttributeSet attrs, int defStyle) {		super(context, attrs, defStyle);		DisplayMetrics dm = new DisplayMetrics();		((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);		screenWidth = dm.widthPixels;		scroller = new Scroller(context);		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();		mTouchLimit = screenWidth / 4;	}		/**	 * 设置可滑动的最大值	 * @param limit	 */	public void setTouchLimit(int limit){		this.mTouchLimit = limit;	}		/**	 * 获取可滑动的最大值	 * @return	 */	public int getTouchLimit(){		return this.mTouchLimit;	}	/**	 * 退回原位	 */	public void slideBack(){		if(hasSlided && itemView != null){			scrollXBy(itemView, -mTouchLimit);			hasSlided = false;		}	}		/**	 * 分发事件,主要做的是判断点击的是那个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);			}			// 如果已经侧滑了,我们直接返回			if(hasSlided){				return super.dispatchTouchEvent(event);			}			downX = (int) event.getX();			downY = (int) event.getY();			slidePosition = pointToPosition(downX, downY);			// 无效的position, 不做任何处理			if (slidePosition == AdapterView.INVALID_POSITION) {				return super.dispatchTouchEvent(event);			}			// 获取我们点击的item view			itemView = getChildAt(slidePosition - getFirstVisiblePosition());			if(itemView instanceof SlideListItem){				SlideListItem item = (SlideListItem) itemView;				int count = item.getChildCount();				mTouchLimit = 0;				for (int i = 0; i < count; i++) {					if(i > 0){						mTouchLimit += item.getChildAt(i).getMeasuredWidth();					}				}			}			break;		}		case MotionEvent.ACTION_MOVE: {//			Log.d("test", "downX : " + downX + ", event.getX() : " + event.getX() + ", mTouchSlop : " + mTouchSlop);			if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY					|| (downX - event.getX() > mTouchSlop && Math							.abs(event.getY() - downY) < mTouchSlop)) {				isSlide = true;			}			break;		}		case MotionEvent.ACTION_UP:			recycleVelocityTracker();			break;		}		return super.dispatchTouchEvent(event);	}	/**	 * 处理我们拖动ListView item的逻辑	 */	@Override	public boolean onTouchEvent(MotionEvent ev) {		Log.d("test", "hasSlided : " + hasSlided + ", isSlide : " + isSlide);		// 如果已经侧滑了,回归原位		if(hasSlided){			scrollXBy(itemView, -mTouchLimit);			hasSlided = false;			// 拦截事件,不继续处理			return false;		}		if (isSlide && slidePosition != AdapterView.INVALID_POSITION) {			requestDisallowInterceptTouchEvent(true);			addVelocityTracker(ev);			final int action = ev.getAction();			int x = (int) ev.getX();			switch (action) {			case MotionEvent.ACTION_DOWN:				break;			case MotionEvent.ACTION_MOVE:								MotionEvent cancelEvent = MotionEvent.obtain(ev);	            cancelEvent.setAction(MotionEvent.ACTION_CANCEL |	                       (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));	            onTouchEvent(cancelEvent);	            				int deltaX = downX - x;				downX = x;				// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚				scrollXBy(itemView, deltaX);								return true;  //拖动的时候ListView不滚动			case MotionEvent.ACTION_UP:				int velocityX = getScrollVelocity();				if (velocityX < -SNAP_VELOCITY) {					scrollLeft();				} else {					scrollByDistanceX();				}				recycleVelocityTracker();				// 手指离开的时候就不响应左右滚动				isSlide = false;				break;			}		}//		boolean result = super.onTouchEvent(ev);//		Log.d("test", "result : " + result);		//否则直接交给ListView来处理onTouchEvent事件				return super.onTouchEvent(ev);	}		/**	 * 让指定的view滚动x位置,设置左右边界,view最多滚动到边界位置	 * @param view	 * @param x	 */	private void scrollXBy(View view, int x){		// 如果已经滑动了最大值,并希望继续向左滑,忽略		if(view.getScrollX() >= mTouchLimit && x >= 0) return;		// 如果已经回到原位,并希望继续想右划,忽略		if(view.getScrollX() <= 0 && x <= 0) return;				if(view.getScrollX() + x > mTouchLimit){			x = mTouchLimit - view.getScrollX();		}else if(view.getScrollX() + x < 0){			x = -view.getScrollX();		}		view.scrollBy(x, 0);	}		@Override	public void computeScroll() {		// 调用startScroll的时候scroller.computeScrollOffset()返回true,		if (scroller.computeScrollOffset()) {			// 让ListView item根据当前的滚动偏移量进行滚动			itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());						postInvalidate();		}	}		/**	 * 向左滑动,根据上面我们知道向左滑动为正值	 */	private void scrollLeft() {		final int delta = (mTouchLimit - itemView.getScrollX());		// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item		scroller.startScroll(itemView.getScrollX(), 0, delta, 0,				Math.abs(delta));		postInvalidate(); // 刷新itemView		hasSlided = true;	}	/**	 * 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动	 */	private void scrollByDistanceX() {		// 如果向左滚动的距离大于屏幕的二分之一,就让其删除		if (itemView.getScrollX() >= mTouchLimit / 2) {			scrollLeft();		} else {			// 滚回到原始位置,为了偷下懒这里是直接调用scrollTo滚动			itemView.scrollTo(0, 0);		}	}	/**	 * 添加用户的速度跟踪器	 * 	 * @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,实现主要功能,dispatchTouchEvent进行事件分发,由自身的onTouchEvent方法处理侧滑事件。其中

if(itemView instanceof SlideListItem){				SlideListItem item = (SlideListItem) itemView;				int count = item.getChildCount();				mTouchLimit = 0;				for (int i = 0; i < count; i++) {					if(i > 0){						mTouchLimit += item.getChildAt(i).getMeasuredWidth();					}				}			}
通过计算item的长度,设定侧滑的最大偏移量。ScrollXBy方法,防止侧滑越界。

/**	 * 让指定的view滚动x位置,设置左右边界,view最多滚动到边界位置	 * @param view	 * @param x	 */	private void scrollXBy(View view, int x){		// 如果已经滑动了最大值,并希望继续向左滑,忽略		if(view.getScrollX() >= mTouchLimit && x >= 0) return;		// 如果已经回到原位,并希望继续想右划,忽略		if(view.getScrollX() <= 0 && x <= 0) return;				if(view.getScrollX() + x > mTouchLimit){			x = mTouchLimit - view.getScrollX();		}else if(view.getScrollX() + x < 0){			x = -view.getScrollX();		}		view.scrollBy(x, 0);	}

SlideListItem:继承LinearLayout,主要通过重写onLayout方法使其中的子View不压缩变形。

package com.jcking.slidepauselistview.view;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.LinearLayout;public class SlideListItem extends LinearLayout {	public SlideListItem(Context context) {		this(context, null);	}	public SlideListItem(Context context, AttributeSet attrs) {		super(context, attrs);	}	@Override	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {		super.onMeasure(widthMeasureSpec, heightMeasureSpec);		int count = getChildCount();		for (int i = 0; i < count; i++) {			measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);		}	}		@Override	public boolean dispatchTouchEvent(MotionEvent event) {		Log.d("test", "event : " + event);		return super.dispatchTouchEvent(event);	}	@Override	protected void onLayout(boolean changed, int l, int t, int r, int b) {		int margeLeft = 0;		int size = getChildCount();		for (int i = 0; i < size; i++) {			View view = getChildAt(i);			if (view.getVisibility() != View.GONE) {				int childWidth = view.getMeasuredWidth();				// 将内部子孩子横排排列				view.layout(margeLeft, 0, margeLeft + childWidth,						view.getMeasuredHeight());				margeLeft += childWidth;			}		}	}}


SlideAdapter:在Adapter中绑定Click事件,分别监听。

package com.jcking.slidepauselistview;import java.util.List;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.TextView;public class SlideAdapter extends BaseAdapter {		private List<String> mData;	private LayoutInflater mInflater;	private ViewHolder mHolder;	private OnSlideClickListener mListener;		class ViewHolder{		TextView tv;		View front;		Button btnClock;		Button btnDelete;	}		public SlideAdapter(Context context, List<String> data, OnSlideClickListener listener){		this.mData = data;		this.mInflater = LayoutInflater.from(context);		this.mListener = listener;	}	@Override	public int getCount() {		return mData == null ? 0 : mData.size();	}	@Override	public String getItem(int position) {		return mData == null ? null : mData.get(position);	}	@Override	public long getItemId(int position) {		return position;	}	@Override	public View getView(int position, View convertView, ViewGroup parent) {		if(convertView == null){			convertView = mInflater.inflate(R.layout.item_listview, null);			mHolder = new ViewHolder();			mHolder.tv = (TextView) convertView.findViewById(R.id.tv);			mHolder.front = convertView.findViewById(R.id.front);			mHolder.btnClock = (Button) convertView.findViewById(R.id.btnClock);			mHolder.btnDelete = (Button) convertView.findViewById(R.id.btnDelete);			convertView.setTag(mHolder);		}else{			mHolder = (ViewHolder) convertView.getTag();		}		mHolder.tv.setText(getItem(position));		// 使隐藏的按钮数量不一样,测试可滑动范围		mHolder.btnClock.setVisibility(position%2==0 ? View.GONE : View.VISIBLE);				if(mListener != null){			final int pos = position;			final View item = convertView;			mHolder.btnClock.setOnClickListener(new OnClickListener() {								@Override				public void onClick(View v) {					mListener.onClockClick(pos, item);				}			});			mHolder.btnDelete.setOnClickListener(new OnClickListener() {								@Override				public void onClick(View v) {					mListener.onDeleteClick(pos, item);				}			});			mHolder.front.setOnClickListener(new OnClickListener() {								@Override				public void onClick(View v) {					mListener.onItemClick(pos, item);				}			});		}		return convertView;	}	public interface OnSlideClickListener{		public void onItemClick(int position, View item);		public void onClockClick(int position, View item);		public void onDeleteClick(int position, View item);	}}

MainActivity:简单调用

package com.jcking.slidepauselistview;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Toast;import com.jcking.slidepauselistview.SlideAdapter.OnSlideClickListener;import com.jcking.slidepauselistview.view.SlidePauseListView;public class MainActivity extends Activity implements OnSlideClickListener {	private SlidePauseListView slideCutListView;	private SlideAdapter adapter;	private List<String> dataSourceList = new ArrayList<String>();	@Override	protected void onCreate(Bundle savedInstanceState) {		super.onCreate(savedInstanceState);		setContentView(R.layout.activity_main);		init();	}	private void init() {		slideCutListView = (SlidePauseListView) findViewById(R.id.slideCutListView);		for (int i = 0; i < 20; i++) {			dataSourceList.add("滑动删除" + i);		}		adapter = new SlideAdapter(this, dataSourceList, this);		slideCutListView.setAdapter(adapter);	}	@Override	public void onItemClick(int position, View item) {		// TODO Auto-generated method stub		Toast.makeText(this, "点击条目  " + position, Toast.LENGTH_SHORT).show();	}	@Override	public void onClockClick(int position, View item) {		// TODO Auto-generated method stub		Toast.makeText(this, "点击闹钟  " + position, Toast.LENGTH_SHORT) .show();	}	@Override	public void onDeleteClick(int position, View item) {		// TODO Auto-generated method stub		Toast.makeText(this, "点击删除  " + position, Toast.LENGTH_SHORT) .show();		slideCutListView.slideBack();		dataSourceList.remove(position);		adapter.notifyDataSetChanged();	}}

activity_main:主视图布局

<?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" >    <com.jcking.slidepauselistview.view.SlidePauseListView        android:id="@+id/slideCutListView"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:cacheColorHint="@android:color/transparent"        android:divider="#2b2b2b"        android:dividerHeight="0.5dp"        android:listSelector="@android:color/transparent" >    </com.jcking.slidepauselistview.view.SlidePauseListView></LinearLayout>

item_listview:item布局

<?xml version="1.0" encoding="UTF-8"?><com.jcking.slidepauselistview.view.SlideListItem xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:background="#acacac"    android:orientation="horizontal" >    <!-- 这里一定要设置android:layout_width属性为充满屏幕  -->    <LinearLayout        android:id="@+id/front"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:clickable="true"        android:gravity="center_vertical"        android:orientation="horizontal"        android:padding="5dp" >        <TextView            android:id="@+id/tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="测试数据"            android:textSize="18sp" />    </LinearLayout>    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:gravity="center"        android:orientation="horizontal" >        <Button            android:id="@+id/btnClock"            android:layout_width="wrap_content"            android:layout_height="match_parent"            android:background="#ff0000"            android:text="闹铃"            android:textColor="#ffffff" />        <Button            android:id="@+id/btnDelete"            android:layout_width="wrap_content"            android:layout_height="match_parent"            android:background="#00ffff"            android:text="删除"            android:textColor="#ffffff" />    </LinearLayout></com.jcking.slidepauselistview.view.SlideListItem>

提示:

1.item布局中,第一个一定要充满屏幕


问题:

1.灵敏度不高,不能像QQ一样完全精确识别手势


参考:http://blog.csdn.net/xiaanming/article/details/17539199

          http://blog.csdn.net/jwzhangjie/article/details/39006007  


点击此处,下载源码

  相关解决方案