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

Android自定义视图缩放时如何防止偏移至原点?

问题:自定义View缩放时偏移至原点,无法保持居中

我参照Android官方教程实现了自定义View的缩放功能,缩放本身正常,但缩小时视图会偏移到(0,0)位置,希望缩放时保持原居中位置。以下是我的自定义View代码:

public class MyCustomView extends View {

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.0f;

    private Paint paint;

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

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

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

    private void init(Context context) {
        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float cx = getWidth()/2f;
        float cy = getHeight()/2f;

        canvas.save();
        canvas.scale(mScaleFactor, mScaleFactor);

        canvas.drawRect(cx - 100, cy - 100, cx + 100, cy + 100, paint);

        canvas.restore();
    }

    private class ScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            invalidate();
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);
        return true;
    }
}

缩放偏移演示

解决方案

问题根源

当前代码中canvas.scale(mScaleFactor, mScaleFactor)默认以原点(0,0)作为缩放锚点,缩放时会将原本居中的矩形向原点拉扯,导致偏移。要保持居中,需将缩放锚点设置为视图中心,或根据手势动态调整锚点。


方案1:固定围绕视图中心缩放

只需修改onDraw方法,给scale方法添加视图中心作为锚点参数:

@Override
protected void onDraw(Canvas canvas) {
    float cx = getWidth() / 2f;
    float cy = getHeight() / 2f;

    canvas.save();
    // 将缩放锚点设置为视图中心,替代默认的(0,0)
    canvas.scale(mScaleFactor, mScaleFactor, cx, cy);

    canvas.drawRect(cx - 100, cy - 100, cx + 100, cy + 100, paint);

    canvas.restore();
}

修改后,缩放操作会始终围绕视图中心进行,矩形不会再偏移至原点。


方案2:支持围绕双指手势中心点缩放(更符合用户习惯)

如果需要让用户通过双指在任意位置缩放,视图围绕手势中心点变化,需添加偏移量记录并调整画布变换:

  1. 添加成员变量记录手势偏移:
private float mPosX = 0f;
private float mPosY = 0f;
  1. 修改ScaleListener,记录手势中心点并调整偏移量:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scaleFactor = detector.getScaleFactor();
        float focusX = detector.getFocusX();
        float focusY = detector.getFocusY();

        // 计算偏移量,确保手势中心点缩放后位置不变
        mPosX = focusX - (focusX - mPosX) * scaleFactor;
        mPosY = focusY - (focusY - mPosY) * scaleFactor;

        mScaleFactor *= scaleFactor;
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

        invalidate();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        // 记录手势开始时的中心点
        mPosX = detector.getFocusX();
        mPosY = detector.getFocusY();
        return true;
    }
}
  1. 修改onDraw方法,结合平移和缩放实现手势中心缩放:
@Override
protected void onDraw(Canvas canvas) {
    float cx = getWidth() / 2f;
    float cy = getHeight() / 2f;

    canvas.save();
    // 先平移到手势偏移位置,再缩放,最后平移回原中心确保初始位置正确
    canvas.translate(mPosX, mPosY);
    canvas.scale(mScaleFactor, mScaleFactor);
    canvas.translate(-cx, -cy);

    canvas.drawRect(cx - 100, cy - 100, cx + 100, cy + 100, paint);

    canvas.restore();
}

这种方式能让缩放操作更贴合用户的交互预期,双指在哪里就围绕哪里缩放。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 22:05:20