如何通过StickerView库实现贴纸边界外的缩放旋转功能?
Hey there! I’ve worked with the StickerView library before, so I can walk you through how to add that Instagram Stories-style external control point for scaling and rotating stickers. Here’s a step-by-step solution:
First, we’ll subclass the original StickerView to add our external control point and handle the custom touch logic. This keeps our changes isolated and avoids modifying the library’s core code directly.
import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.util.TypedValue; import android.view.MotionEvent; import android.view.ViewGroup; import android.widget.ImageView; public class ExtendedStickerView extends StickerView { private ImageView outerControlPoint; private float initialScale; private float initialRotation; private float initialDistanceToCenter; private float initialAngleToCenter; private boolean isOuterControlPressed = false; public ExtendedStickerView(Context context) { super(context); initOuterControl(); } public ExtendedStickerView(Context context, AttributeSet attrs) { super(context, attrs); initOuterControl(); } // Initialize the external yellow control point private void initOuterControl() { outerControlPoint = new ImageView(getContext()); // Replace this with your own yellow control point drawable outerControlPoint.setImageResource(R.drawable.ic_yellow_control_point); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( dpToPx(24), dpToPx(24) ); addView(outerControlPoint, params); } // Helper to convert dp to pixels private int dpToPx(int dp) { return (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics() ); } // Position the external control point relative to the current sticker @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (getCurrentSticker() == null) return; RectF stickerBounds = getCurrentSticker().getBound(); // Place the control point outside the top-right corner of the sticker float controlX = stickerBounds.right + dpToPx(16); float controlY = stickerBounds.top - dpToPx(16); outerControlPoint.layout( (int) (controlX - outerControlPoint.getWidth()/2), (int) (controlY - outerControlPoint.getHeight()/2), (int) (controlX + outerControlPoint.getWidth()/2), (int) (controlY + outerControlPoint.getHeight()/2) ); } // Handle touch events for the external control point @Override public boolean onTouchEvent(MotionEvent event) { float touchX = event.getX(); float touchY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // Check if the user tapped the external control point Rect controlHitBox = new Rect(); outerControlPoint.getHitRect(controlHitBox); if (controlHitBox.contains((int) touchX, (int) touchY)) { isOuterControlPressed = true; Sticker currentSticker = getCurrentSticker(); if (currentSticker != null) { // Save initial state for calculations initialScale = currentSticker.getScale(); initialRotation = currentSticker.getRotation(); PointF stickerCenter = currentSticker.getCenter(); initialDistanceToCenter = (float) Math.hypot( touchX - stickerCenter.x, touchY - stickerCenter.y ); initialAngleToCenter = (float) Math.toDegrees( Math.atan2(touchY - stickerCenter.y, touchX - stickerCenter.x) ); } return true; } break; case MotionEvent.ACTION_MOVE: if (isOuterControlPressed && getCurrentSticker() != null) { Sticker currentSticker = getCurrentSticker(); PointF stickerCenter = currentSticker.getCenter(); // Calculate current distance and angle from touch to sticker center float currentDistance = (float) Math.hypot( touchX - stickerCenter.x, touchY - stickerCenter.y ); float currentAngle = (float) Math.toDegrees( Math.atan2(touchY - stickerCenter.y, touchX - stickerCenter.x) ); // Calculate new scale (based on distance change) float newScale = initialScale * (currentDistance / initialDistanceToCenter); // Limit scale to avoid extreme values newScale = Math.max(0.5f, Math.min(newScale, 3.0f)); // Calculate new rotation (based on angle change) float newRotation = initialRotation + (currentAngle - initialAngleToCenter); // Update the sticker's properties currentSticker.setScale(newScale); currentSticker.setRotation(newRotation); invalidate(); return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: isOuterControlPressed = false; break; } // Pass other touch events to the parent class for default handling return super.onTouchEvent(event); } }
Replace instances of StickerView in your layout XML with your new ExtendedStickerView:
<com.yourpackage.ExtendedStickerView android:id="@+id/sticker_view" android:layout_width="match_parent" android:layout_height="match_parent" />
- Control Point Positioning: The
onLayoutmethod ensures the external control point stays anchored to the sticker’s top-right corner as the sticker moves. You can adjust the offset (dpToPx(16)) to move it closer or farther from the sticker. - Scale Limits: The code includes a scale range (0.5x to 3x) to prevent stickers from becoming too small or large. Adjust these values to match your needs.
- Touch Isolation: We handle the external control’s touch events separately and return
trueto consume those events, so they don’t interfere with the default sticker controls.
If the original Sticker class doesn’t expose methods like getCenter() or setScale(), you may need to modify the library’s source code to make these properties public, or use reflection to access them.
内容的提问来源于stack exchange,提问作者Aditya

