You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Canvas绘制矩形:如何实现触摸旋转与缩放?

Implementing Rotation and Scaling for a Canvas Rectangle via Touch Events

Great question! Implementing rotation and scaling for a Canvas-drawn rectangle via touch events in Android involves tracking the rectangle's state and handling multi-touch gestures properly. Here's a complete, actionable solution:

Step 1: Create a Custom View

We'll build a custom View to manage the rectangle's state (position, size, rotation) and handle touch events. This keeps all related logic encapsulated.

Full Custom View Code

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class TransformableRectView extends View {
    private Paint rectPaint;
    
    // Rectangle state (matches your original drawRect(300,300,500,500))
    private float centerX = 400f; // Center of the original rectangle
    private float centerY = 400f;
    private float originalWidth = 200f; // Width of original rect (500-300)
    private float originalHeight = 200f; // Height of original rect (500-300)
    private float scaleFactor = 1f; // Current scale multiplier
    private float rotationAngle = 0f; // Current rotation in degrees

    // Touch tracking variables
    private int activePointerId = INVALID_POINTER_ID;
    private float initialFingerDistance;
    private float initialFingerAngle;
    private float initialScale;
    private float initialRotation;

    public TransformableRectView(Context context) {
        super(context);
        init();
    }

    public TransformableRectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        // Set up paint for the rectangle
        rectPaint = new Paint();
        rectPaint.setColor(Color.BLUE);
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeWidth(5f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Save the canvas state to revert after drawing
        canvas.save();

        // 1. Translate canvas to the rectangle's center
        canvas.translate(centerX, centerY);
        // 2. Rotate canvas by the current rotation angle
        canvas.rotate(rotationAngle);
        // 3. Scale canvas by the current scale factor
        canvas.scale(scaleFactor, scaleFactor);

        // Draw the rectangle centered at the origin of the transformed canvas
        float halfWidth = originalWidth / 2;
        float halfHeight = originalHeight / 2;
        canvas.drawRect(-halfWidth, -halfHeight, halfWidth, halfHeight, rectPaint);

        // Restore canvas to its original state
        canvas.restore();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // Track the first finger that touches down
                activePointerId = event.getPointerId(0);
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                // Second finger touches down: initialize gesture state
                initialFingerDistance = calculateFingerDistance(event);
                initialFingerAngle = calculateFingerAngle(event);
                initialScale = scaleFactor;
                initialRotation = rotationAngle;
                break;

            case MotionEvent.ACTION_MOVE:
                if (event.getPointerCount() == 2) {
                    // Calculate current gesture values
                    float currentDistance = calculateFingerDistance(event);
                    float currentAngle = calculateFingerAngle(event);

                    // Update scale factor (limit to 0.5x to 3x to prevent extreme sizes)
                    scaleFactor = initialScale * (currentDistance / initialFingerDistance);
                    scaleFactor = Math.max(0.5f, Math.min(scaleFactor, 3f));

                    // Update rotation angle (delta between current and initial angle)
                    rotationAngle = initialRotation + (currentAngle - initialFingerAngle);

                    // Trigger redraw to show updated rectangle
                    invalidate();
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                // Reset active pointer when fingers lift
                activePointerId = INVALID_POINTER_ID;
                break;
        }
        // Return true to indicate we're handling the touch event
        return true;
    }

    // Calculate distance between two fingers
    private float calculateFingerDistance(MotionEvent event) {
        float xDiff = event.getX(0) - event.getX(1);
        float yDiff = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
    }

    // Calculate angle between two fingers (relative to x-axis)
    private float calculateFingerAngle(MotionEvent event) {
        float xDiff = event.getX(1) - event.getX(0);
        float yDiff = event.getY(1) - event.getY(0);
        return (float) Math.toDegrees(Math.atan2(yDiff, xDiff));
    }
}

Step 2: Add the View to Your Layout

Add the custom view to your XML layout like this:

<com.yourpackage.TransformableRectView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Key Explanations

  • Canvas Transformations: Instead of manually calculating rotated/scaled rectangle coordinates, we modify the canvas's coordinate system. This simplifies drawing drastically—note the order: translate first, then rotate, then scale.
  • Multi-Touch Handling: We track two fingers to detect scaling (via distance change) and rotation (via angle change between the two fingers).
  • State Preservation: When the second finger touches down, we save the initial gesture state (distance, angle, current scale/rotation), then calculate the delta to update the rectangle's state as fingers move.
  • Scale Limits: We clamp the scale factor between 0.5x and 3x to avoid the rectangle becoming too small or large—adjust these values based on your needs.

Optional Enhancements

  • Add single-finger drag to move the rectangle around the screen.
  • Add visual feedback (like resize handles) to indicate the rectangle is interactive.
  • Persist the rectangle's state (position, scale, rotation) across app restarts.

内容的提问来源于stack exchange,提问作者Resmi Krishnan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:44:01