本篇博客讲解的是自定义View之侧滑面板,应用场景:QQ,知乎,效果图如下
内容摘要
- 了解ViewDragHelper 的产生及解决的问题
- 掌握ViewDragHelper 的使用步骤
- 掌握属性动画的使用
- 掌握状态更新及事件回调的用法
实现最简单的拖拽
实现最简单的拖拽
在创建DragLayout 时,继承FrameLayout,这里需要注意两个问题
为什么不继承ViewGroup,因为继承ViewGroup 需要重写onMeasure()和实现onLayout()方法,自己实现子view 的测量和摆放,在这里我们不需要自己去做测量和摆放,而FrameLayout 已经对这两个方法进行了具体实现,所以继承FrameLayout 更加简单省事
为什么不继承RelativeLayout,因为这里我们只需要层级关系,不需要相对关系,继承RelativeLayout界面效果是一样的,但RelativeLayout 对FrameLayout 多了相对关系的计算,效率会低一些,所以选择继承FrameLayout
|
|
串联构造方法
DragLayout 实例化时需要做一些初始化操作,如果我们定义一个init()方法,则我们需要在三个构造方法中都调用init()方法,这样非常麻烦,我们可以通过串连三个构造方法的方式实现只调用一次init()方法这样无论是代码创建还是布局在xml 中都能调用到我们的初始化代码
|
|
ViewDragHelper 简介
我们要实现拖拽的效果,则需要自己去解析Touch 事件的ACTION_DOWN,ACTION_MOVE,ACTION_UP,相当的麻烦。所以Google 在2013 年的IO 大会上发布了ViewDragHelper 这个类,用来解决滑动拖拽问题,用这个类可以非常简单的实现view 的拖拽
创建ViewDragHelper
由于eclipse 创建项目时,为我们添加的android-support-v4.jar 没有包含ViewDragHelper,我们需要将最新的android-support-v4.jar 拷贝到libs 下面,然后clean 一下工程。
在这里我们需要关联android-support-v4.jar 的源码,通过配置文件的方法来关联源码
在libs 下面创建一个android-support-v4.jar.properties 的文件
android-support-v4.jar.properties 中的内容为src = V4 包源码路径
我们只需要在第三个构造方法中实现ViewDragHelper 的实例即可
|
|
ViewDragHelper 三个参数的创建的方法源码中的mTouchSlop 表示触摸的最小敏感范围,越小越敏感即在界面拖动的瞬间变化量大于mTouchSlop 时才可以成功触发拖拽事件
|
|
触摸事件转交
ViewDragHelper 创建成功了,但它和DragLayout 并没有任何关系,我们需要让它们建立关系
|
|
重写onInterceptTouchEvent 方法,将触摸事件交给ViewDragHelper 判断是否拦截,这样它们就建立了关系,事件拦截后,还需要对拦截到的事件进行处理,注意返回值必须是true
|
|
处理回调事件
ViewDragHelper 在处理触摸事件时会通过传入的callback 给我们反馈,通过对回调方法的处理即可实现简单的拖拽
|
|
DragLayout 布局到 xml 中
给左面板和主面板设置不同的背景颜色便于拖拽时观察效果,运行工程,即可实现简单的拖拽
限定拖拽范围
现在左面板和主面板可以任意拖动,本节要实现左面板不动,拖动时,主面板在一定范围内拖动
OnFinishInflate()介绍
onFinishInflate()在控件inflate 完成时会被调用,可以在这个方法中查找子控件
- 可以通过findViewById()的方式查找子控件
- 可以通过子view 索引的方式查找子控件
这里采用第二种方式
|
|
获取控件宽高
在onMeasure()方法中可以获取到控件的宽高,也可以在onSizeChanged()方法中去获取宽高,onMeasure()方法调用后会检测宽高值有没有变化,有变化才调用onSizeChanged()方法,无变化则不调用,所以onSizeChanged()调用的次数比onMeasure()少,在这里我们在onSizeChanged()方法中去获取宽高,同时计算出拖拽范围为宽度的60%
|
|
限定主面板的拖动范围
对callback 中的其它几个方法进行重写
|
|
回调方法中的getViewHorizontalDragRange(View child)方法返回拖拽的范围,但不会真正限定这个范围,只要返回一个大于零的值即可。
在ViewDragHelper 源码中,computeSettleDuration()会调用这个返回值来计算动画执行的时长,checkTouchSlop()方法会调用这个返回值检查左面板,主面板是否可以被滑动,所以需要返回一个大于0的值才能实现拖动。
如果返回值为0,左面板,主面板中不能有子view 或子view 没有对touch 事件做处理,最后触摸还是会交给ViewDragHelper 处理,所以也能实现拖动
|
|
限定主面板的拖拽范围,当建议的值left 小于0 时,让left 等于0,大于mRange 时等于mRange,然后再将left 返回
|
|
当控件位置变化时会调用onViewPositionChanged()方法,可以在此方法中做伴随动画,状态更新,事件回调,left 表示最新的水平位置,dx 表示刚刚发生的水平变化量。
此时左面板还可以任意拖动,为了实现拖动左面板时界面表现为拖动主面板,可以对changedView 进行判断,如果changedView 是左面板,则通过layout()把左面板放回到原来的位置,然后把变化量dx 累加给主面板,再通过layout()方法来移动主面板
|
|
注意:由于onViewPositionChanged()方法调用前调用了offsetLeftAndRight()方法,此方法在低版本中没有重绘界面,并且在高版本中也有一个bug,最后一帧没有被绘制,所以需要手动调用一次invalidate(),否则在低版本中无法实现拖拽效果
结束动画
拖拽过程中当手指抬起时,需要实现一个打开,关闭面板的动画,结束动画可以在 onViewReleased()方法实现
跳转的结束动画
onViewReleased()方法在松手之后会被调用,此时可以做结束动画,结束动画只需要考虑需要打开的
情况,其它则为需要关闭情况
- 当水平方向的速度等于 0,并且主面板此时左边的位置在拖拽范围中轴线的右边则需要执行打开动
画,即 mMainContent.getLeft() > mRange*0.5f
- 当水平方向的速度大于 0 时,则需要执行打开动画
- 其它情况则需要执行关闭动画
|
|
open(),close()创建为 DragLayout 的方法,这样方便外界调用
|
|
平滑的结束动画
首先实现平滑的打开动画,在这里需要用到 ViewDragHelper 提供的一个方法smoothSlideViewTo(child,finalLeft,finalTop),三个参数的意思分别是:
- child 需要平滑移动的 view
- finalLeft 需要移动到的终点左边位置
- finalTop 需要移动到的终点的上边位置
smoothSlideViewTo()方法的返回值为 true,表示位置不是最终位置,需要重绘界面
重载一个 open(boolean isSmooth)方法,用参数 isSmooth 标识是调用平滑动画还是跳转动画,open()方法则直接调用 open(true),默认为平滑动画
|
|
注意:smoothSlideViewTo()方法返回 true,需要重绘界面,此时不建议使用 invalidate(),因为在动画的过程中可能会丢帧,推荐使用 ViewCompat.postInvalidateOnAnimation(this),参数一定要传子 view 所在的容器,因为只有容器才知道子 view 的具体位置
重绘命令调用后,还需要重写 computScroll()方法,重绘时,系统会在 draw()方法后调用 computScroll(),在该方法中调用 ViewDragHelper 的维持动画的方法
continueSettling(deferCallbacks)参数 deferCallbacks 表示是否延迟画下一帧,此处传入 true,返回值表示是否已经移动到最终位置,如果为 true,还没有移动到最终位置,需要重绘界面,这样 computeScroll()方法就会不断的调用,界面也就会不断的重绘,直到移动到最终位置
|
|
同样的道理,关闭的平滑动画只需要修改 finalLeft = 0 即可
|
|
伴随动画
分解伴随动画
伴随动画是拖拽的过程中,左面板,主面板会跟随拖拽百分比所做的动画,该动画需要在onViewPositionChanged()回调方法中实现
- 左面板:缩放动画,平移动画,透明度动画
- 主面板:缩放动画
- 背景: 亮度变化
实现伴随动画
创建一个方法 dispatchDragEvent(),在 onViewPositionChanged()方法中调用
|
|
实现左面板的缩放动画
|
|
- 第 3 行通过主面板左边位置与拖拽范围的相除可以得到一个 0.0f ->1.0f 的比例值,因为在整个拖拽过
程中,主面板左边位置的变化是引起一系列变化的原因 - 第 7-10 行可以推出一个公式 start + percent(end - start),即通过 percent 的变化可以计算出 start 到 end 间
的任意值。源码 FloatEvaluator.java 中已经提供了这么一个方法,将其拷贝到代码中,即第 20-23 行 - 第 12-16 行为了兼容低版本引入 nineoldandroid.jar 中的 ViewHelper 做属性动画
同理可以实现其它伴随动画
|
|
- 第 27 行叠加模式 PorterDuff.Mode.SRC_OVER 表示直接叠加在上面
- 第 30-48 行 ArgbEvaluator.java 源码中拷贝的估值方法,api18 以上的代码才有透明度的过滤
状态更新及事件回调
状态分析
拖拽的状态可以分为:
- 打开状态
- 关闭状态
- 拖拽状态
通过枚举定义这三种状态,且定义默认状态为关闭
|
|
事件回调分析
定义一个事件回调接口,事件回调和状态密切相关
- 打开状态时回调 onOpen()方法
- 关闭状态时回调 onClose()方法
拖拽中回调 onDraging(float percent)方法,并将拖拽百分比传出去
|
|
实现状态更新及事件回调
通过拖拽百分比可以判断当前的状态,在 dispatchDragEvent()方法中实现状态更新及事件回调
- 百分比为 0,则为关闭状态
- 百分比为 1,则为打开状态
- 其它百分比,则为拖拽状态
事件回调需要先做空判断,拖拽状态调用频率高,直接调用即可,打开和关闭可以判断上次状态和当
前状态是否一致,不一致则调用
|
|
触摸优化
填充界面数据
1.修改主界面 xml,左面板,主面板分别加入 ListView 及头像
|
|
2.ListView 数据源
|
|
|
|
|
|