前言
手机QQ应该是很普及的App了,看到QQ消息栏对话框列表的每个子项左滑的时候会弹出删除、置顶图标。like this:
于是突发奇想:想要自己实现一个这样的效果。
很显然的,这样的效果实现要依赖Android的事件分发机制,于是我先从Android事件分发入手。对于事件分发还不太熟悉的朋友可以参考Android事件分发机制学习。
下面开工!
List Item
首先,针对ListView的每个Item自定义一个MyItemLayout。代码如下:
1 | public class MyItemLayout extends LinearLayout { |
每个Item有2个直接子节点,第一个是contentView,第二个是menuView。通过设置contentView的leftMargin,达到显示Menu的效果。初始时,leftMargin为0,Menu完全隐藏。当滑动时,leftMargin逐渐缩小(因为是负数),当leftMargin等于minLeftMargin时,Menu完全显示。
本来有种想法(参考郭霖大神的博客)是采用线程Sleep的方式来达到滑动效果的。代码如下:
1 | private class ScrollTask extends AsyncTask<Integer, Integer, Integer> { |
但是后面产生的实际效果不太好,滑动的时候总是有点卡顿的感觉,于是便弃用了,后面还是采用的Scroller类。
下面贴上每个Item的布局:
1 |
|
content id即是第一个子节点,menu id为第二个子节点。
Adapter
1 | public class MyAdapter extends BaseAdapter { |
Adapter类比较简单,这里不做过多的赘述。
Model类
MyModel类主要是为了模仿QQ会话写的一个类。
1 | public class MyModel { |
自定义ListView
下面便是最关键的一个:自定义ListView,覆写onTouchEvent方法,实现滑动删除。
1 | public class MyListView extends ListView { |
在这个项目中,触摸事件分以下几种情况:
- 当前没有Menu正在显示
- ACTION_DOWN记录相关信息,准备接收ACTION_MOVE、ACTION_UP事件。
- ACTION_MOVE中,判断是横滑还是竖滑。若是横滑,则调用setLeftMargin(),这个时候若一直滑动,Menu会慢慢地显示出来。后面会返回true,主要是为了拦截最后的super.onTouchEvent(ev)不执行。若是竖滑,则直接调用super.onTouchEvent(ev),这个时候若一直滑动,则是ListView的上下滑动了。
- ACTION_UP中,我们只需要判断是否要显示,若显示则调用smoothOpenMenu(),并返回true(这里返回true或者false都没有实际的意义)。若是不需要,则直接super.onTouchEvent(ev)。
- 当前有Menu正在显示
- ACTION_DOWN,若当前点击的Item不是Menu正在显示的Item,那么直接smoothCloseMenu(),并且返回false。返回false后MOVE、UP等事件会统统不接收。
- 若是正在点击的Item,那么首先设置为横滑,并且返回true,等待后续的触摸事件。
- ACTION_MOVE因为在DOWN的时候设置了mTouchState = TOUCH_STATE_X;那么会执行到if内部,因为Menu正在显示,所以不会调用setLeftMargin(),并且直接返回true,即后面的super.onTouchEvent(ev)也不会调用。
- ACTION_UP中判断Menu是否要关闭,若关闭则调用smoothCloseMenu(),并且返回true。若是不需要,则直接返回super.onTouchEvent(ev)。
这里是复杂的地方,需要对各种情况进行判断,然后执行相应的逻辑。我写了好多次,改过好多次QAQ…
Tips
- 在ACTION_DOWN的分支中,返回false会直接截断后面MOVE、UP等事件的接收。
- 在ACTION_MOVE与ACTION_UP的返回值,为true为false,并没有特别实际的效果,仅仅是为了返回,以此来截断super.onTouchEvent(ev)的执行。
最后
下面上效果图:
看起来效果也还不错,是吧?