1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Android ListView侧滑item 仿QQ删除效果

Android ListView侧滑item 仿QQ删除效果

时间:2020-10-11 05:45:06

相关推荐

Android ListView侧滑item 仿QQ删除效果

尊重原创,转载请注明(/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 {/*** 当前滑动的ListViewposition*/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来设置响应左右滑动事件*/@Overridepublic 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 viewitemView = 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的逻辑*/@Overridepublic 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);}@Overridepublic void computeScroll() {// 调用startScroll的时候puteScrollOffset()返回true,if (puteScrollOffset()) {// 让ListView item根据当前的滚动偏移量进行滚动itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());postInvalidate();}}/*** 向左滑动,根据上面我们知道向左滑动为正值*/private void scrollLeft() {final int delta = (mTouchLimit - itemView.getScrollX());// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动itemscroller.startScroll(itemView.getScrollX(), 0, delta, 0,Math.abs(delta));postInvalidate(); // 刷新itemViewhasSlided = 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() {puteCurrentVelocity(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);}@Overrideprotected 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);}}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Log.d("test", "event : " + event);return super.dispatchTouchEvent(event);}@Overrideprotected 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;}@Overridepublic int getCount() {return mData == null ? 0 : mData.size();}@Overridepublic String getItem(int position) {return mData == null ? null : mData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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() {@Overridepublic void onClick(View v) {mListener.onClockClick(pos, item);}});mHolder.btnDelete.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mListener.onDeleteClick(pos, item);}});mHolder.front.setOnClickListener(new OnClickListener() {@Overridepublic 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>();@Overrideprotected 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);}@Overridepublic void onItemClick(int position, View item) {// TODO Auto-generated method stubToast.makeText(this, "点击条目 " + position, Toast.LENGTH_SHORT).show();}@Overridepublic void onClockClick(int position, View item) {// TODO Auto-generated method stubToast.makeText(this, "点击闹钟 " + position, Toast.LENGTH_SHORT) .show();}@Overridepublic void onDeleteClick(int position, View item) {// TODO Auto-generated method stubToast.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="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><com.jcking.slidepauselistview.view.SlidePauseListViewandroid: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="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#acacac"android:orientation="horizontal" ><!-- 这里一定要设置android:layout_width属性为充满屏幕 --><LinearLayoutandroid: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" ><TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试数据"android:textSize="18sp" /></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:orientation="horizontal" ><Buttonandroid:id="@+id/btnClock"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#ff0000"android:text="闹铃"android:textColor="#ffffff" /><Buttonandroid: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一样完全精确识别手势

参考:/xiaanming/article/details/17539199

/jwzhangjie/article/details/39006007

点击此处,下载源码

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。