【Android】双指旋转手势

发布于:2025-06-04 ⋅ 阅读:(26) ⋅ 点赞:(0)

一,概述

本文参考android.view.ScaleGestureDetector,对双指旋转手势做了一层封装,采用了向量计算法简单实现,笔者在此分享下。

二,实例

如下,使用RotateGestureDetector即可委托,实现旋转手势的简单封装,在对应Callback获取到旋转值设置到View即可。

public class RectView extends FrameLayout {

    private static final String TAG = "RectView";

    private View mRotateView;

    private final ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        @Override
        public boolean onScale(@NonNull ScaleGestureDetector detector) {
            Log.d(TAG, "onScale() called with: detector = [" + detector.getScaleFactor() + "]");
            mRotateView.setScaleX(detector.getScaleFactor());
            mRotateView.setScaleY(detector.getScaleFactor());
            return true;
        }

        @Override
        public boolean onScaleBegin(@NonNull ScaleGestureDetector detector) {
            return super.onScaleBegin(detector);
        }

        @Override
        public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
            super.onScaleEnd(detector);
        }
    });

    private final RotateGestureDetector rotateGestureDetector = new RotateGestureDetector(new RotateGestureDetector.Listener() {
        @Override
        public void onRotateStart(RotateGestureDetector detector) {

        }

        @Override
        public void onRotating(RotateGestureDetector detector) {
            mRotateView.setRotation((float) detector.eulerAngle);
        }

        @Override
        public void onRotateEnd(RotateGestureDetector detector) {

        }
    });

    public RectView(Context context) {
        super(context);

        mRotateView = new View(context);
        mRotateView.setBackgroundColor(Color.GRAY);

        this.addView(mRotateView, new FrameLayout.LayoutParams(100, 100, Gravity.CENTER));
    }

    public RectView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

//    @SuppressLint("ClickableViewAccessibility")
//    @Override
//    public boolean onTouchEvent(MotionEvent event) {
//        rotateGestureDetector.onTouchEvent(event);
//        return true;
//    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        rotateGestureDetector.onTouchEvent(ev);
        scaleGestureDetector.onTouchEvent(ev);
        return true;
    }
}

三,实现

Rotate具体实现如下,仅供参考。

import android.view.MotionEvent;

import androidx.annotation.NonNull;

/**
 * @author :zhong.jw
 * @date :Created in 2023/2/23 13:39
 * 旋转手势相关:采用向量法计算角度
 */
public final class RotateGestureDetector {

    private static final double DEFAULT_LIMIT_START = 3f;

    @NonNull
    private final Listener listener;

    /**
     * 是否旋转中
     */
    public boolean isRotating = false;
    /**
     * 旋转轴点x
     */
    public int focusX;
    /**
     * 旋转轴点y
     */
    public int focusY;
    /**
     * 欧拉角,范围[-180~180]
     */
    public double eulerAngle = 0;

    /**
     * 弧度
     */
    public double radian = 0;

    /**
     * 开始旋转的初始向量x值
     */
    public double x1 = 0;
    /**
     * 开始旋转的初始向量y值
     */
    public double y1 = 0;
    /**
     * 开始旋转的初始向量斜率
     */
    public double k1 = 0;

    public RotateGestureDetector(@NonNull Listener listener) {
        this.listener = listener;
    }


    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getActionMasked();
        int pointCount = event.getPointerCount();

        if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && (pointCount == 2) && !isRotating) {
            double e1x = event.getX(0);
            double e2x = event.getX(1);
            double e1y = event.getY(0);
            double e2y = event.getY(1);
            focusX = (int) (e1x + e2x) / 2;
            focusY = (int) ((e1y + e2y) / 2);
            x1 = e2x - e1x;
            y1 = e2y - e1y;
            k1 = y1 / x1;
            return true;
        }

        if (action == MotionEvent.ACTION_MOVE && pointCount == 2) {
            double e1x = event.getX(0);
            double e2x = event.getX(1);
            double e1y = event.getY(0);
            double e2y = event.getY(1);
            double x2 = e2x - e1x;
            double y2 = e2y - e1y;
            //angle = arccos(ab/(|a||b|))
            radian = Math.acos((x1 * x2 + y1 * y2) / (Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2)) * Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2))));
            // y = k1*x2 > y2 来判断是否属于外角
            eulerAngle = (radian / Math.PI * 180) * (k1 * x2 > y2 ? -1 : 1);
            if (isRotating) {
                listener.onRotating(this);
            }

            if (Math.abs(eulerAngle) >= DEFAULT_LIMIT_START) {
                isRotating = true;
                listener.onRotateStart(this);
            }

            return true;
        }


        if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) && isRotating) {
            isRotating = false;
            listener.onRotateEnd(this);
        }

        return true;
    }

    public interface Listener {

        /**
         * @param detector:旋转信息
         */
        void onRotateStart(RotateGestureDetector detector);

        /**
         * @param detector:旋转信息
         */
        void onRotating(RotateGestureDetector detector);

        /**
         * @param detector:旋转信息
         */
        void onRotateEnd(RotateGestureDetector detector);
    }

}


网站公告

今日签到

点亮在社区的每一天
去签到