鸿蒙手势操控组件,代码已开源!
The following article is from 鸿蒙技术社区 Author 朱伟ISRC
如若转载请联系原公众号
开源地址:
https://gitee.com/isrc_ohos/pinch-image-view-ohos
PinchImageView-ohos 是一个支持多点触控的 ImageView 手势操控组件,通过识别单指双击、双指捏合、单指滑动等手势指令,实现图片的放大、缩小、滑动等效果。
该组件功能丰富且使用简单,被广泛应用于各类图片预览类应用。
01
组件效果展示
图 1:双指捏合效果
图 2:双击效果
图 3:单指双击后单指移动效果
02
Sample 解析
Sample 部分主要负责整体显示布局的搭建。首先为 PinchImageView-ohos 组件设置显示图片,然后将组件对象添加到显示布局中。
下面将详细介绍组件的使用方法:
步骤 1:创建整体的显示布局。
步骤 2:导入相关类并实例化 PinchImageView-ohos 组件对象。
步骤 3:设置显示图片。
//步骤1 创建整体的显示布局
DirectionalLayout directionalLayout = new DirectionalLayout(this);
//步骤2 导入相关类并实例化对象
PinchImageViewnew pinchImageView = new PinchImageViewnew(this);
//步骤3 设置显示图片
pinchImageView.setPixelMap(this, ResourceTable.Media_1111);
//步骤4 将pinchImageView添加到整体显示布局中
directionalLayout.addComponent(pinchImageView);
setUIContent(directionalLayout);
03
Library 解析
手势获取对实现 PinchImageView-ohos 组件的功能尤为重要,此处主要通过 onTouchEvent() 方法来捕捉对应的手势。
主要用到的手势包含:
PRIMARY_POINT_UP(最后一根手指从屏幕上抬起)
PRIMARY_POINT_DOWN(第一根手指触摸屏幕)
OTHER_POINT_DOWN(当一根或多根手指已经触摸屏幕时,另一个手指触摸屏幕 )
OTHER_POINT_UP(一些手指从屏幕上抬起,而一些手指仍留在屏幕上 )
POINT_MOVE(手指在屏幕上移动)
通过监控各类手势的操作顺序和触碰时间等条件,达到识别捏合、滑动、单击、双击等复杂手势的效果。
onTouchEvent() 函数首先通过 TouchEvent.getAction() 方法获取当前的手势,当手势为:
(1)PRIMARY_POINT_UP
//最后一个点抬起或者取消,结束所有模式
if (action == TouchEvent.PRIMARY_POINT_UP || action == TouchEvent.CANCEL) {
//如果之前是缩放模式,还需要结束缩放动画
if (mPinchMode == PINCH_MODE_SCALE) {
scaleEnd();//缩放结束
}
//手势状态置于自由模式
mPinchMode = PINCH_MODE_FREE;
}
(2)PRIMARY_POINT_DOWN
else if (action == TouchEvent.PRIMARY_POINT_DOWN) {
//在缩放动画过程中不允许启动滚动模式
if (!(mScaleAnimator != null && mScaleAnimator.isRunning())) {
//在动画过程中不允许启动滚动模式,停止所有动画
cancelAllAnimator();
//切换到滚动模式
mPinchMode = PINCH_MODE_SCROLL;
//保存触发点的位置用于(5)中的计算
mLastMovePoint.modify(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY());
}
}
(3)OTHER_POINT_DOWN
else if (action == TouchEvent.OTHER_POINT_DOWN) {
//在动画过程中不允许启动缩放模式,停止所有动画
cancelAllAnimator();
//切换到缩放模式
mPinchMode = PINCH_MODE_SCALE;
//保存缩放的两个触发点的位置,用于(5)中的计算
saveScaleContext(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
}
(4)OTHER_POINT_UP
需要判断手指抬起后图片是否处于缩放模式。如果处于缩放模式下,判断识别到的手指是否超过两个。
在剩余手指超过两个(缩放模式未结束)的情况下,第一个触摸的手指抬起,那么让第二个触摸的手指和第三个触摸的手指所在的点作为缩放控制点。
在剩余手指超过两个(缩放模式未结束)的情况下,第二个触摸的手指抬起,那么让第一个触摸的手指和第三个触摸的手指所在的点作为缩放控制点。
如果处于缩放模式下,判断识别到的手指只有一个。此时不能允许它切换到滚动模式,因为图片可能没有在初始的位置上。
else if (action == TouchEvent.OTHER_POINT_UP) {
//多个手指情况下抬起一个手指,此时需要是缩放模式才触发
if (mPinchMode == PINCH_MODE_SCALE) {
//抬起的点如果大于2,那么缩放模式还有效,但是有可能初始点变了,重新测量初始点
if (event.getPointerCount() > 2) {
//如果还没结束缩放模式,但是第一个点抬起了,那么让第二个点和第三个点作为缩放控制点
if (event.getAction() >> 8 == 0) {
event.getPointerPosition(1).getX();
saveScaleContext(event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY(), event.getPointerPosition(2).getX(), event.getPointerPosition(2).getY());
//如果还没结束缩放模式,但是第二个点抬起了,那么让第一个点和第三个点作为缩放控制点
} else if (event.getAction() >> 8 == 1) {
saveScaleContext(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(2).getX(), event.getPointerPosition(2).getY());
}
}
//如果抬起的点等于2,那么此时只剩下一个点,也不允许进入单指模式,因为此时可能图片没有在正确的位置上
}
}
(5)POINT_MOVE
需要判断当前图片的模式。当为滚动模式时,执行 scrollBy() 方法来实现图片的移动效果;当它为缩放模式时,计算两个缩放点的距离和缩放点的中心,并执行 scale() 方法实现图片的缩放效果。
else if (action == TouchEvent.POINT_MOVE) {
if (!(mScaleAnimator != null && mScaleAnimator.isRunning())) {
//在滚动模式下移动
if (mPinchMode == PINCH_MODE_SCROLL) {
//每次移动产生一个差值累积到图片位置上
scrollBy(event.getPointerPosition(0).getX() - mLastMovePoint.position[0], event.getPointerPosition(0).getY() - mLastMovePoint.position[1]);
//记录新的移动点
mLastMovePoint.modify(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY());
//在缩放模式下移动
} else if (mPinchMode == PINCH_MODE_SCALE && event.getPointerCount() > 1) {
//两个缩放点间的距离
float distance = MathUtils.getDistance(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
//保存缩放点中心
float[] lineCenter = MathUtils.getCenterPoint(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
mLastMovePoint.modify(lineCenter[0], lineCenter[1]);
//处理缩放
scale(mScaleCenter, mScaleBase, distance, mLastMovePoint);
}
}
}
②图片操控方法
1、图片缩放
双指捏合:顾名思义是表示两根手指向相反方向移动的操作,该操作可实现图片放大缩小的效果。双指捏合完成图片缩放的功能是由 scale() 方法实现的。
在 scale() 方法体中需要设置各种缩放参数:scaleBase 是缩放系数、scaleCenter 代表图片缩放中点、distance 指两指间距离、lineCenter 是两指中点。
scaleBase 和 distance 相乘会得到缩放比例,图片依旧缩放比例进行变化。
private void scale(Point scaleCenter, float scaleBase, float distance, Point lineCenter) {
if (!isReady()) {
return;
}
//计算图片从fit center状态到目标状态的缩放比例
float scale = scaleBase * distance;
Matrix matrix = MathUtils.matrixTake();
//按照图片缩放中心缩放,并且让缩放中心在缩放点中点上
matrix.postScale(scale, scale, scaleCenter.position[0], scaleCenter.position[1]);
//让图片的缩放中点跟随手指缩放中点
matrix.postTranslate(lineCenter.position[0] - scaleCenter.position[0], lineCenter.position[1] - scaleCenter.position[1]);
//应用变换
mOuterMatrix.setMatrix(matrix);
MathUtils.matrixGiven(matrix);
dispatchOuterMatrixChanged();
//重绘
invalidate();
}
单指双击:表示用单根手指双击屏幕的操作,该操作可实现图片放大缩小的效果,单指双击完成图片缩放的功能是由 doubleTap() 方法实现的。
在 doubleTap() 方法体中我们初始化了一个缩放动画的对象 mScaleAnimator(),它有两个参数分别为 mOuterMatrix(开始矩阵)和 animEnd(结束矩阵)。
开始矩阵表示图片原来的位置与大小;结束矩阵表示图片缩放后的位置与大小,是根据放大比例和双击点位置确定的。
private void doubleTap(float x, float y) {
...
//开始计算缩放动画的结果矩阵
Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);
//计算还需缩放的倍数
animEnd.postScale(nextScale / currentScale, nextScale / currentScale, x, y);
//将放大点移动到控件中心
animEnd.postTranslate(displayWidth / 2f - x, displayHeight / 2f - y);
RectFloat testBound = MathUtils.rectFTake(0,0,mp.getImageInfo().size.width,mp.getImageInfo().size.height);
...
//清理当前可能正在执行的动画
cancelAllAnimator();
//启动矩阵动画
mScaleAnimator = new ScaleAnimator(mOuterMatrix, animEnd);
mScaleAnimator.start();
...
}
2、图片在缩放状态下移动
单指滑动表示手指在屏幕上完成矢量平移,是图片移动的唯一方式。该功能是通过 scrollBy() 方法实现的。
以实现图片左右移动为例,在 scrollBy() 方法中,需要判断缩放状态下图片位移的最大距离,有以下几种不同的情况:
图片移动后,左侧边缘超出控件的左侧边缘,图片无法移动。
图片移动后,右侧边缘超出控件的右侧边缘,图片无法移动。
图片移动后,两侧都未超出控件边缘的情况下,将以手指触碰点作为控制点,对图片进行水平移动。
图 6:图片移动的最大距离
public boolean scrollBy(float xDiff, float yDiff) {
...
if (bound.right - bound.left < displayWidth) {
xDiff = 0;
//如果图片左边在移动后超出控件左边
} else if (bound.left + xDiff > 0) {
//如果在移动之前是没超出的,计算应该移动的距离
if (bound.left < 0) {
xDiff = -bound.left;
//否则无法移动
} else {
xDiff = 0;
}
//如果图片右边在移动后超出控件右边
} else if (bound.right + xDiff < displayWidth) {
//如果在移动之前是没超出的,计算应该移动的距离
if (bound.right > displayWidth) {
xDiff = displayWidth - bound.right;
//否则无法移动
} else {
xDiff = 0;
}
}
...
}
推荐阅读:
苹果新系统很鸿蒙!iPad终于能写代码了,iPhone竟成异地恋神器