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
相关产品推荐
相关产品推荐

