android ListView的item侧滑删除
转载地址:https://blog.csdn.net/zxc123e/article/details/54585609
Demo下载地址:https://download.csdn.net/download/as425017946/10633657
首先看一下效果图,有个直观认识
主要功能就是ListView的item可以侧滑,出来一个删除按钮,点击delete就删除该item。
这是一个相对比较综合的例子,来看看动手之前需要准备哪些知识。
1. 对自定义View要有一定的知识基础,参看View绘制流程
2. 事件的拦截以及反拦截的相关知识,以便很好的解决事件冲突问题,关于事件机制,可以参看android事件处理机制
3. 滑动器Scroller的使用,参看Scroller简单用法
4. 自定义View中的接口回调(View状态变化时执行回调)
下面我们一步一步来实现这个功能。
1.自定义ListView中item的布局类
item的布局文件item_slide.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
其中根元素是SlideLayout.java类,这是我们自定义的一个布局类,继承自FrameLayout,代码如下:
public class SlideLayout extends FrameLayout { private View contentView; private View menuView; private int viewHeight; //高是相同的 private int contentWidth; private int menuWidth; //滑动器 private Scroller scroller; public SlideLayout(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(context); } /** * 布局文件加载完成时被调用 */ @Override protected void onFinishInflate() { super.onFinishInflate(); contentView = findViewById(R.id.content); menuView = findViewById(R.id.menu); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); viewHeight = getMeasuredHeight(); contentWidth = contentView.getMeasuredWidth(); menuWidth = menuView.getMeasuredWidth(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); menuView.layout(contentWidth, 0, contentWidth+menuWidth, viewHeight); } private float startX; private float startY; private float downX; private float downY; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: float endX = event.getX(); float endY = event.getY(); //计算偏移量 float distanceX = endX - startX; int toScrollX = (int) (getScrollX()-distanceX); //屏蔽非法值 if (toScrollX < 0 ) { toScrollX = 0; } if (toScrollX > menuWidth) { toScrollX = menuWidth; } System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX()); scrollTo(toScrollX,getScrollY()); startX = event.getX(); break; case MotionEvent.ACTION_UP: break; } return true; }}
完成上面的代码之后,我们可以将这个布局文件(item_slide.xml)放在Activity中显示出来,可以看到这时候我们左滑每个item后可以显示出删除按钮,这个按钮之所以会显示出是因为我们已经在onLayout方法中将这个删除按钮正好放在内容View的右侧了,所以不滑动时是看不到的,只有滑动时才显示出来。
2.手势抬起时item自动回弹
现在滑动是可以了,但是我们希望滑动距离大于删除按钮宽度一半后,手抬起时可以直接显示删除按钮,当滑动距离小于删除按钮的一半时,直接回弹将删除按钮隐藏起来,所以我们修改onTouchEvent方法如下
@Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = startX = event.getX(); downY = startY = event.getY(); break; case MotionEvent.ACTION_MOVE: float endX = event.getX(); float endY = event.getY(); //计算偏移量 float distanceX = endX - startX; int toScrollX = (int) (getScrollX()-distanceX); //屏蔽非法值 if (toScrollX < 0 ) { toScrollX = 0; } if (toScrollX > menuWidth) { toScrollX = menuWidth; } System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX()); scrollTo(toScrollX,getScrollY()); startX = event.getX(); break; case MotionEvent.ACTION_UP: if (getScrollX() > menuWidth/2) { //打开menu openMenu(); }else { closeMenu(); } break; } return true; } /** * 打开menu菜单 */ public void openMenu() { int dx = menuWidth-getScrollX(); scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY()); invalidate(); } /** * 关闭菜单 */ public void closeMenu() { //0表示menu移动到的目标距离,目标位置-起始位置 int dx = 0-getScrollX(); scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY()); invalidate(); } @Override public void computeScroll() { super.computeScroll(); if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); invalidate(); } }
回弹使用了Scroller滑动器,invalidate()方法执行时会调用computeScroll()方法,computeScroll()方法每次执行都回调一小段距离,scroller.computeScrollOffset()判断是否需要继续回弹,这个判断里面的invalidate()执行会导致这个过程循环执行,直到回弹结束。
3.放入ListView中显示
将item_slide.xml放在ListView中显示,这个就比较简单了,直接上代码。
public class SlideActivity extends Activity { private ListView listView; private ArrayList mDatas; private MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_slide); listView = (ListView) findViewById(R.id.main_list); mDatas = new ArrayList<>(); for (int i = 0; i < 50; i++) { mDatas.add(new MyContent("content"+i)); } myAdapter = new MyAdapter(this, mDatas); listView.setAdapter(myAdapter); } class MyAdapter extends BaseAdapter { private Context content; private ArrayList datas; private MyAdapter(Context context, ArrayList datas) { this.content = context; this.datas = datas; } @Override public int getCount() { return datas.size(); } @Override public Object getItem(int position) { return datas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder=null; if (convertView == null) { convertView = LayoutInflater.from(content).inflate(R.layout.item_slide, null); viewHolder = new ViewHolder(); viewHolder.contentView= (TextView) convertView.findViewById(R.id.content); viewHolder.menuView = (TextView) convertView.findViewById(R.id.menu); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.contentView.setText(datas.get(position).getContent()); return convertView; } } static class ViewHolder { public TextView contentView; public TextView menuView; }}
4.解决item滑动和ListView滑动冲突问题
到这里,我们将SlideLayout放入ListView中显示出来,当我们滑动item时,在同时上下滑动时发现item是不会回弹的,这是什么原因?
这就需要你对事件机制了解清楚了,你再上下滑动的时候,滑动事件已经被ListView消耗了,SlideLayout中的onTouchEvent就得不到执行了,这是需要判断(也有重新ListView的),如果在左右滑动时,SlideLayout就需要向父级ListView请求不要拦截事件,如果是上下滑动,就不需要理会了,按默认的来。
所以我们修改SlideLayout中的onTouchEvent方法如下,Move事件处代码:
case MotionEvent.ACTION_MOVE: float endX = event.getX(); float endY = event.getY(); //计算偏移量 float distanceX = endX - startX; int toScrollX = (int) (getScrollX()-distanceX); //屏蔽非法值 if (toScrollX < 0 ) { toScrollX = 0; } if (toScrollX > menuWidth) { toScrollX = menuWidth; } System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX()); scrollTo(toScrollX,getScrollY()); startX = event.getX(); float dx = Math.abs(event.getX()-downX); float dy = Math.abs(event.getY()-downY); if (dx > dy && dx > 6) { //事件反拦截,使父ListView的事件传递到自身SlideLayout getParent().requestDisallowInterceptTouchEvent(true); } break;
这样我们在左右滑动item时同时上下滑动是不起作用的。
5.解决item点击事件和item滑动事件的冲突
这是,我们给item添加点击事件后,发现item又不能进行滑动了,原因同样是事件被别人消耗了,这次是被SlideLayout中的TextView消耗了,这时我们同样需要判断,如果是滑动就拦截事件,如果是点击就放行。
重新onInterceptTouchEvent方法
@Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = startX = event.getX(); downY = startY = event.getY(); break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(event.getX()-downX); float dy = Math.abs(event.getY()-downY); if (dx > dy && dx > 6) { //拦截事件 return true; } break; case MotionEvent.ACTION_UP: break; } return super.onInterceptTouchEvent(event); }
6.限制只允许一个item显示删除按钮
通过暴露接口的方式,在Activity中设置监听器,当SlideLayout滑动时,调用相关状态的方法,来控制item删除按钮的显示和隐藏。
public interface OnStateChangeListener { void onOpen(SlideLayout slideLayout); void onMove(SlideLayout slideLayout); void onClose(SlideLayout slideLayout); } public OnStateChangeListener onStateChangeListener; public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) { this
Activity中的全部代码:
public class SlideActivity extends Activity { private ListView listView; private ArrayList mDatas; private MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_slide); listView = (ListView) findViewById(R.id.main_list); mDatas = new ArrayList<>(); for (int i = 0; i < 50; i++) { mDatas.add(new MyContent("content"+i)); } myAdapter = new MyAdapter(this, mDatas); listView.setAdapter(myAdapter); } class MyAdapter extends BaseAdapter { private Context content; private ArrayList datas; private MyAdapter(Context context, ArrayList datas) { this.content = context; this.datas = datas; } @Override public int getCount() { return datas.size(); } @Override public Object getItem(int position) { return datas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder=null; if (convertView == null) { convertView = LayoutInflater.from(content).inflate(R.layout.item_slide, null); viewHolder = new ViewHolder(); viewHolder.contentView= (TextView) convertView.findViewById(R.id.content); viewHolder.menuView = (TextView) convertView.findViewById(R.id.menu); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.contentView.setText(datas.get(position).getContent()); viewHolder.contentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(content, "click "+((TextView)v).getText(), Toast.LENGTH_SHORT).show(); } }); final MyContent myContent = datas.get(position); viewHolder.menuView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { datas.remove(myContent); notifyDataSetChanged(); } }); SlideLayout slideLayout = (SlideLayout) convertView; slideLayout.setOnStateChangeListener(new MyOnStateChangeListener()); return convertView; } public SlideLayout slideLayout = null; class MyOnStateChangeListener implements SlideLayout.OnStateChangeListener { @Override public void onOpen(SlideLayout layout) { slideLayout = layout; } @Override public void onMove(SlideLayout layout) { if (slideLayout != null && slideLayout !=layout) { slideLayout.closeMenu(); } } @Override public void onClose(SlideLayout layout) { if (slideLayout == layout) { slideLayout = null; } } } } static class ViewHolder { public TextView contentView; public TextView menuView; }}
MyContent.java
public class MyContent { private String content; public MyContent(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}
SlideLayout全部代码:
public class SlideLayout extends FrameLayout { private View contentView; private View menuView; private int viewHeight; //高是相同的 private int contentWidth; private int menuWidth; //滑动器 private Scroller scroller; public SlideLayout(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(context); } /** * 布局文件加载完成时被调用 */ @Override protected void onFinishInflate() { super.onFinishInflate(); contentView = findViewById(R.id.content); menuView = findViewById(R.id.menu); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); viewHeight = getMeasuredHeight(); contentWidth = contentView.getMeasuredWidth(); menuWidth = menuView.getMeasuredWidth(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); menuView.layout(contentWidth, 0, contentWidth+menuWidth, viewHeight); } private float startX; private float startY; private float downX; private float downY; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = startX = event.getX(); downY = startY = event.getY(); break; case MotionEvent.ACTION_MOVE: float endX = event.getX(); float endY = event.getY(); //计算偏移量 float distanceX = endX - startX; int toScrollX = (int) (getScrollX()-distanceX); //屏蔽非法值 if (toScrollX < 0 ) { toScrollX = 0; } if (toScrollX > menuWidth) { toScrollX = menuWidth; } System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX()); scrollTo(toScrollX,getScrollY()); startX = event.getX(); float dx = Math.abs(event.getX()-downX); float dy = Math.abs(event.getY()-downY); if (dx > dy && dx > 6) { //事件反拦截,使父ListView的事件传递到自身SlideLayout getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_UP: if (getScrollX() > menuWidth/2) { //打开menu openMenu(); }else { closeMenu(); } break; } return true; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = startX = event.getX(); downY = startY = event.getY(); if (onStateChangeListener != null) { onStateChangeListener.onMove(this); } break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(event.getX()-downX); float dy = Math.abs(event.getY()-downY); if (dx > dy && dx > 6) { //拦截事件 return true; } break; case MotionEvent.ACTION_UP: break; } return super.onInterceptTouchEvent(event); } /** * 打开menu菜单 */ public void openMenu() { int dx = menuWidth-getScrollX(); scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY()); invalidate(); if (onStateChangeListener != null) { onStateChangeListener.onOpen(this); } } /** * 关闭菜单 */ public void closeMenu() { //0表示menu移动到的目标距离 int dx = 0-getScrollX(); scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY()); invalidate(); if (onStateChangeListener != null) { onStateChangeListener.onClose(this); } } @Override public void computeScroll() { super.computeScroll(); if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); invalidate(); } } public interface OnStateChangeListener { void onOpen(SlideLayout slideLayout); void onMove(SlideLayout slideLayout); void onClose(SlideLayout slideLayout); } public OnStateChangeListener onStateChangeListener; public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) { this.onStateChangeListener = onStateChangeListener; }}
更多相关文章
- android触屏事件处理onInterceptTouchEvent
- android ViewFilpper(二)
- Android(安卓)SeekBar控件
- 关于android监听H5发送的事件实现方法。
- Android(安卓)getevent/sendevent详解
- 【Android】广播大全 Intent Action 事件
- TabActivity子类中处理返回键事件
- ScaleImageView图片缩放查看器
- Android手势滑动实现ImageView缩放图片大小