Android移动、缩放和旋转手势实现

Android的部分图片编辑应用中需要对图片进行移动、缩放和旋转,这些变化都依赖于触摸手势实现,而本文主要阐述移动、缩放和旋转手势的简单实现。

一、移动

首先需要从触摸事件(MotionEvent)中获取每个手指(Pointer)的坐标,随后计算这些坐标的中心(重心)位置,那么本次触摸事件与前一次触摸事件的中心距离就是手势的移动距离:

java 复制代码
// calc center coordinates
float centerX = 0;
float centerY = 0;
for (int index = 0; index < pointerCount; index++) {
    centerX += event.getX(index);
    centerY += event.getY(index);
}
centerX /= pointerCount;
centerY /= pointerCount;

// calc translation
float translateX = centerX - mPrevCenterX;
float translateY = centerY - mPrevCenterY;

mPrevCenterX = centerX;
mPrevCenterY = centerY;

二、缩放

同样手势缩放也需要计算触摸事件的中心,其次计算每一个手指坐标与中心的距离,然后计算这些距离的平均值,那么本次触摸事件平均中心距离与上一次触摸事件的比值就是本次手势的缩放比例:

java 复制代码
// omit calc center coordinates

// calc mean distance
double sumDistance = 0;
for (int index = 0; index < pointerCount; index++) {
    float vx = event.getX(index) - centerX;
    float vy = event.getY(index) - centerY;

    // calc distance to center coordinates
    sumDistance += Math.sqrt(vx * vx + vy * vy);
}
float meanDistance = (float) (sumDistance / pointerCount);

// calc scale
float scale = mPrevMeanDistance < 1e-5 ? 1 : meanDistance / mPrevMeanDistance;

mPrevMeanDistance = meanDistance;

三、旋转

手势旋转同样需要计算触摸事件的中心,然后计算中心到每一个手指坐标的向量,计算每一个手指向量相对于上一次触摸事件中对应的手指向量的旋转角度,最后计算这些旋转角度的平均值,这个平均值就是本次手势的旋转角度:

java 复制代码
// omit calc center coordinates

// calc rotate degrees
double sumRotateDegrees = 0;
for (int index = 0; index < pointerCount; index++) {
    float vx = event.getX(index) - centerX;
    float vy = event.getY(index) - centerY

    // calc angle between vectors
    int pointerId = event.getPointerId(index);
    sumRotateDegrees = Math.toDegrees(angleBetweenVectors(mPrevVx[pointerId], mPrevVy[pointerId], vx, vy));

    mPrevVx[pointerId] = vx;
    mPrevVy[pointerId] = vy;
}
float rotateDegrees = (float) (sumRotateDegrees / pointerCount);

两个向量叉乘公式:\(\boldsymbol v_1*\boldsymbol v_2=\sin\alpha|\boldsymbol v_1||\boldsymbol v_2|=v_{x1}*v_{y2}-v_{y1}*v_{x2}\),那么两个向量之间的夹角:

\[\begin{flalign*} &\alpha=arcsin(\frac{\boldsymbol v_1*\boldsymbol v_2}{|\boldsymbol v_1||\boldsymbol v_2|})& \end{flalign*} \]

java 复制代码
private static double cross(float vx1, float vy1, float vx2, float vy2) {
    return vx1 * vy2 - vy1 * vx2;
}

private static double angleBetweenVectors(float vx1, float vy1, float vx2, float vy2) {
    double dist1 = Math.sqrt(vx1 * vx1 + vy1 * vy1);
    double dist2 = Math.sqrt(vx2 * vx2 + vy2 * vy2);

    if (dist1 < 1e-5 || dist2 < 1e-5) {
        return 0;
    }
    return Math.asin(cross(vx1, vy1, vx2, vy2) / dist1 / dist2);
}

不管是移动、缩放和旋转都需要保证本次触摸事件手指数量与上一次相等,如果不相等计算出来的值则跳动较大。

四、完整示例

本文的完整示例放在github仓库中,主要实现了图片的移动、旋转和缩放效果:

相关推荐
studyForMokey33 分钟前
【Android面试】窗口机制专题
android·面试·职场和发展
用户013201436031 小时前
Android 资源管理与常用布局详解|基础入门
android
陆业聪2 小时前
从 OpenClaw 到 Android:Harness Engineering 是怎么让 Agent 变得可用的
android·人工智能·ai编程
stevenzqzq4 小时前
颜色透明度转换技术文档(Android/Compose)
android
巴黎没有摩天轮Li4 小时前
Android JVMTI 接入流程
android
2501_915909065 小时前
iOS 抓包不越狱,代理抓包 和 数据线直连抓包两种实现方式
android·ios·小程序·https·uni-app·iphone·webview
城东米粉儿5 小时前
Android VCL 和 NAL笔记
android
常利兵5 小时前
从0到1,解锁Android WebView混合开发新姿势
android·华为·harmonyos
背包客(wyq)6 小时前
基于Android手机的语音数据采集系统(语音数据自动上传至电脑端)
android·网络