另外,本文尝试做出一些改变,这次使用视频来演示效果,视频很小,只有2M不到,期待大家的反馈。 李政 的博客地址:http://blog.csdn.net/lz8362 ListView 可以说是最常用的控件了,所谓在平凡中创建不平凡,各种 ListView 的衍生版本层出不穷。 在商业应用中,一个 item 上堆放的信息不能太多,item 本身的作用在于提供给用户快速选择的权利,信息堆放太多,反而无法让用户快速决定,所以简洁明了才是真。 但是对于一些在场景中必要的功能按键,就需要找到合适的放置位置,尤其是现在大家公司里的美工们都是缺乏激情的MM,多给她们提供一些布局选择,你脱单的希望就增大了一些。 QQ列表的效果一开始并没有吸引我,直到我发现我的测试手机(联想S650)中通讯录列表的item也是可以左右滑动。我的好奇心才被激起了。下面就来讲讲怎么实现这个功能。 先来个演示视频,看看效果: 我们把布局分成三部分:左侧布局,中央布局,右侧布局。中央布局就是在应用中用来正常展示,左侧和右侧布局是隐藏布局,样式如下: 左侧布局,中央布局,右侧布局都是独立的布局,他们之间互不干扰。 我们如何把它们组合起来呢,这就需要重写 onMeasure() 和 onLayout() 方法了,所以我们来自定义一个 FrameLayout 来放置这三个布局。因为我们需要的是一个滑动的效果,所以给这个自定义控件添加一个 Scroller 来管理滑动。这样基本属性和对象我们就搭建出来了: 之所以设置 isLeftExist 和 isRightExist,是为了提高控件的利用率,缺少哪一个布局,就禁止相反方向的滑动。 也是对程序逻辑的一种保护。 这个布局控件继承了 FrameLayout,在xml编辑器中布局时,我们都知道 FramgLayout 的特点,控件的顺序是从左向右依次放置的,所以我们在重写构造函数时,要调用 addView 把左中右三个布局按从左到右的顺序添加上。 Scroller 作为滑动管理器,我们也需要在构造函数中声明,创建时我们可以选择传或者不传滑动插值器的类型,默认不传的话,就是匀减速插值器 DecelerateInterpolator,这里我设置为反弹插值器BounceInterpolator: 接下来,我们先重写 onMeasure() 方法,onMeasure() 方法是用来计算布局的大小的,也是最先被调用的方法,只有知道的布局的大小,设备才能正常绘制出我们的布局。 在这里我们不需要特别声明三部分布局的高宽,只需要声明宽是自适应,高为设备适应后的固定尺寸就行。这样做可以保证布局的正常展示,不会出现压缩现象。在声明过程中,我们需要使用 MeasureSpe.makeMeasureSpec 来指定宽高(这个方法是 onMeasure() 原有形参调用的方法,在这里我们要保持数据的一致性,所以也要使用这个方法),它需要两个参数,一个是 大小size,一个是 模式mode ,其中 MeasureSpec.UNSPECIFIED 表示当前模式为自适应,MeasureSpec.EXACTLY 表示当前模式为确定值或者是充满(match_parent)。 下一步重写 onLayout() 方法,放置布局。因为手机屏幕左上方的位置是(0,0)点,屏幕左侧为负值,右侧为正值,而我们的左中右三个布局,必须要依次放置,View的layout() 方法中只使用的左上角和右下角的坐标,这样我们就可以得出左中右三布局的位置坐标: 首先明白一点,滑动的实际原理,就是布局坐标发生变化,在不断的重绘过程中,产生有规律的位移。这样我们就需要两个要点:
很幸运,Scroller 提供给我们了滑动时的位置变化,滑动的样式,滑动的时机,以及滑动的结束判断标记,所以我们只要伺候好 Scroller 就行了。 之前我们已经在构造函数中初始化了一个 Scroller 对象,下面为了让我们的控件可以滚动起来,我们需要重写控件的 computeScroll() 方法,顾名思义,是计算滑动距离的,这个方法在重绘时会被触发,但这个方法在 ViewGroup 中本身是没有具体操作,所以我们看不到什么变化。现在我们需要在这里重新获得当前位移的距离,并调用 postInvalidate() 进行重绘。位移的距离由 Scroller.getCurrX() 提供。这个方法可以实时获得当前移动轨迹下X轴的坐标变化。移动轨迹由 Scroller.startScroller() 触发,它有五个参数: 滚动发起后,我们可以通过 computeScrollOffset() 来判断当前滚动是否结束,如果是,则为false,不是为true。有了坐标点的移动轨迹,我们就可以在滚动过程中不停地重新设置布局的坐标,重绘布局,从而实现视觉上的滚动效果。 因为我们手指滑动的操作是左右滑动,所以在计算移动距离时,直接让布局移动 Scroller.getCurrX() 个距离就行,而当关闭布局时,直接移动 - 中央布局.getLeft() , 就是代码的 baseX - Scroller.getCurrX() 个距离就行,为什么取负值,因为负值表示是向相反的方向滑动。 ListView 方面,我们只需要重写 onTouchEvent() 就行了,对用户触摸事件进行区分,同时为了实现上下滑动时恢复原来位置,我们需要分别记录当前视图和历史视图,当前位置和历史位置。在对应的 MotionEvent的Action() 中作如下操作: 一、MotionEvent.ACTION_DOWN:
二、MotionEvent.ACTION_MOVE:
三、MotionEvent.ACTION_UP: 作为点击状态的最后一步,这里我们需要根据当前视图的滑动状态,来做一下补偿操作。即判断当前隐藏的视图是否完全显示出来或隐藏掉。详细判断大家可在代码中看到。这里我考虑到的场景有如下几个: 向左滑动:
向右滑动:
整个控件的开发思路就是这样了,对于滑动操作,没有什么复杂的,Android已经给你提供了 Scroller 来完成这一系列操作。我们可以按照如下的流程图,快速制作出项目中需要的控件: |
|