Android触摸事件(二)-TouchUtils,触摸辅助工具类
概述
此类的主要作用是封装了一些触摸事件需要常用的操作,只需要实现简单的接口即可以使用.实现操作如下:
界面拖动(基于单点触摸的移动事件) 界面的缩放(基于两点触摸的移动事件)此类只是一个辅助工具类,并不是必须的也不需要继承此类就可以使用.
此类基于
AbsTouchEventHandle
类回调的事件基础,需要AbsTouchEventHandle
先处理基本的触摸事件,再通过此辅助类实现拖动的操作
不管是拖动还是缩放,本质是基于坐标数据的,所以坐标数据的记录是必然的,因此需要记录的坐标数据如下:
//单点触摸拖动
//按下事件的坐标
private float mDownX = 0f;
private float mDownY = 0f;
//抬起事件的坐标
private float mUpX = 0f;
private float mUpY = 0f;
//多点触摸缩放
//多点触控缩放按下坐标
private float mScaleFirstDownX = 0f;
private float mScaleFirstDownY = 0f;
private float mScaleSecondDownX = 0f;
private float mScaleSecondDownY = 0f;
//多点触摸缩放抬起的坐标
private float mScaleFirstUpX = 0f;
private float mScaleFirstUpY = 0f;
private float mScaleSecondUpX = 0f;
private float mScaleSecondUpY = 0f;
关于拖动
原理
拖动的原理比较好理解,主要是通过跟随触摸点坐标的变化而达到拖动的效果
实现拖动的方式有多种,这里使用的方式为原数据与变动数据分开操作的方式.因此需要保存界面偏移量的变量.
//记录X轴方向的偏移量
float mDrawOffsetX;
//记录Y轴方向的偏移量
float mDrawOffsetY;
实现过程
关键变量定义
移动偏移量:
MOVE 事件结束后新坐标相对于 DOWN 事件时坐标的偏移量
原始偏移量:界面在 DOWN 事件发生之前已经存在的偏移量
实际偏移量:以界面最初加载的状态为原始状态,任何时候对原始状态的偏移量都为界面当前的实际偏移量
,原始偏移量即为上一次的实际偏移量(产生的移动偏移量将使此值变化)
每一次ACTION_MOVE
事件会造成界面偏移,产生一次偏移量.在ACTION_MOVE
事件结束后将本次产生的移动偏移量
与原始偏移量
合并,生成新的界面偏移量,此偏移量即为界面实际的偏移量
.
具体过程如下:
计算移动后位置与按下时位置的偏移量 moveX-downX,moveY-downY 计算新的界面实际偏移量(原始偏移量+移动偏移量) 保存新的实际偏移量即可 重绘界面
整个拖动过程改变的只是界面的偏移变量,
界面原始的任何坐标数据
不会被改变,因此绘制界面是实际的绘制方式应该是:
//设绘制元素的原始坐标为 X,Y; 实际偏移量为 offsetX,offsetY
//绘制该元素
draw(X+offsetX, Y+offsetY);
由此可确定,偏移量是存在正负值的
,正负值表示相反方向的偏移;而 mBDrawOffsetX
与mDrawOffsetY
原始值必定为0
事件处理回调
理论上界面的拖动是无任何限制的,但是在实际的操作中,存在一些实际的要求导致界面的拖动范围存在限制.界面的拖动是实际上是为了显示不在屏幕中的其它元素,当拖动超过一定范围时,屏幕中将不存在任何元素,偏移范围已经远远超过元素所在的坐标位置,这是没有意义的.
在界面拖动时,将提供对应的回调接口,用以确定界面是否可以拖动.当界面确认可以拖动时,才进行重绘工作,当界面不允许拖动时,只会保留最后一次
ACTION_MOVE
事件中可重绘的界面.
对外的回调接口如下:
/**
* 移动事件处理接口
*/
public interface IMoveEvent {
/**
* 是否可以实现X轴的移动
*
* @param moveDistanceX 当次X轴的移动距离(可正可负)
* @param newOffsetX 新的X轴偏移量(若允许移动的情况下,此值实际上即为上一次偏移量加上当次的移动距离)
* @return
*/
public abstract boolean isCanMovedOnX(float moveDistanceX, float newOffsetX);
/**
* 是否可以实现Y轴的移动
*
* @param moveDistacneY 当次Y轴的移动距离(可正可负)
* @param newOffsetY 新的Y轴偏移量(若允许移动的情况下,此值实际上即为上一次偏移量加上当次的移动距离)
* @return
*/
public abstract boolean isCanMovedOnY(float moveDistacneY, float newOffsetY);
/**
* 移动事件
*
* @param suggestEventAction 建议处理的事件,值可能为{@link MotionEvent#ACTION_MOVE},{@link MotionEvent#ACTION_UP}
* @return
*/
public abstract void onMove(int suggestEventAction);
/**
* 无法进行移动事件
*
* @param suggetEventAction 建议处理的事件,值可能为{@link MotionEvent#ACTION_MOVE},{@link MotionEvent#ACTION_UP}
*/
public abstract void onMoveFail(int suggetEventAction);
}
其中除了确认是否可拖动的回调接口,还包括了移动前的回调接口onMove()
,无法移动时(可能由于条件不允许等)的回调接口onMoveFail()
偏移量计算
对于偏移量计算规则,上面已经提过了.这里需要补充的是另外几个要点.
首先,偏移量的计算在整个MOVE
事件中是一直都在发生的,因为每隔一段时间ACTION_MOVE
事件就会触发一次,每触发一次回调则会计算一次偏移量(但不一定会重绘
);
每次偏移量的计算都是以 DOWN 事件按下时的坐标为基础的
,这说明了每次计算得到的移动偏移量
是相对ACTION_DOWN
时坐标的偏移量.
其次,由于ACTION_MOVE
事件的触发是不稳定的,即使按住坐标没有改变还是会被触发(猜测是每隔一段时间就会触发,而不是基于坐标的变化,原理没有细究确定).一些移动中可能会产生十几甚至几十次ACTION_MOVE
事件,而且某些情况下偏移量会非常小(仅有1-2个像素也有可能),所以做了一些的排除措施.
当偏移量达到一定的数值时(
计算偏移量的绝对值,因为偏移量可能是负值
),才进行一次界面的重绘,以此减少重绘的次数
最后,在整个ACTION_MOVE
事件时,每一次有效的偏移量改变时,才会记录到实际偏移量中,之后界面会被重新绘制.
根据以上的流程,我们需要记录的偏移量变量包括:
//任何时候绘制需要的偏移量
protected float mDrawOffsetY = 0f;
protected float mDrawOffsetX = 0f;
//移动过程中临时保存的移动前的偏移量
protected float mTempDrawOffsetX = 0f;
protected float mTempDrawOffsetY = 0f;
其中
mDrawOffset
变量是任何时候绘制界面时使用的偏移量(不管在ACTION_MOVE
事件中还是ACTION_UP
); 而mTempDrawOffset
则是在ACTION_MOVE
移动时保存的移动前的偏移量,用以计算某次移动事件之后的实际偏移量
实现
/**
* 根据移动的距离计算是否重新绘制
*
* @param moveDistanceX X轴方向的移动距离(可负值)
* @param moveDistanceY Y轴方向的移动距离(可负值)
* @param invalidateAction 重绘的行为标志
*/
private boolean invalidateInSinglePoint(float moveDistanceX, float moveDistanceY, int invalidateAction) {
if (mMoveEvent == null) {
return false;
}
//此处做大于5的判断是为了避免在检测单击事件时
//有可能会有很微小的变动,避免这种情况下依然会造成移动的效果
if (Math.abs(moveDistanceX) > 5 || Math.abs(moveDistanceY) > 5 || invalidateAction == MotionEvent.ACTION_UP) {
//新的偏移量
float newDrawOffsetX = mTempDrawOffsetX + moveDistanceX;
float newDrawOffsetY = mTempDrawOffsetY + moveDistanceY;
//当前绘制的最左边边界坐标大于0(即边界已经显示在屏幕上时),且移动方向为向右移动
if (!mMoveEvent.isCanMovedOnX(moveDistanceX, newDrawOffsetX)) {
//保持原来的偏移量不变
newDrawOffsetX = mDrawOffsetX;
}
//当前绘制的顶端坐标大于0且移动方向为向下移动
if (!mMoveEvent.isCanMovedOnY(moveDistanceY, newDrawOffsetY)) {
//