如何在自定义视图中修复捏缩放焦点?

问题描述 投票:7回答:1

对于我的问题,我在Github准备了a very simple test app

为简单起见,我删除了投掷,滚动约束和边缘效果(实际上在我的真实应用程序中运行良好):

test app screenshot

所以我的测试应用程序中的the custom view仅支持滚动:

mGestureDetector = new GestureDetector(context,
        new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY) {
        mBoardScrollX -= dX;
        mBoardScrollY -= dY;

        ViewCompat.postInvalidateOnAnimation(MyView.this);
        return true;
    }
});

然后用两根手指捏缩放(焦点虽然破了!):

mScaleDetector = new ScaleGestureDetector(context,
        new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    @Override
    public boolean onScale(ScaleGestureDetector scaleDetector) {
        float focusX = scaleDetector.getFocusX();
        float focusY = scaleDetector.getFocusY();
        float factor = scaleDetector.getScaleFactor();

        mBoardScrollX = mBoardScrollX + focusX * (1 - factor) * mBoardScale;
        mBoardScrollY = mBoardScrollY + focusY * (1 - factor) * mBoardScale;
        mBoardScale *= factor;

        ViewCompat.postInvalidateOnAnimation(MyView.this);
        return true;
    }
});

最后,这里的代码绘制了scalled和offsetted游戏板Drawable

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.scale(mBoardScale, mBoardScale);
    canvas.translate(mBoardScrollX / mBoardScale, mBoardScrollY / mBoardScale);
    mBoard.draw(canvas);
    canvas.restore();
}

如果您尝试运行我的应用程序,您会注意到在使用两个手指捏缩放手势缩放游戏板时,变焦焦点会跳跃。

我的理解是,在缩放Drawable时,我需要通过调整mBoardScrollXmBoardScrollY值来平移它 - 以便焦点保持在游戏板坐标中的相同点。所以我的计算是 -

这一点的旧位置是:

-mBoardScrollX + focusX * mBoardScale
-mBoardScrollY + focusY * mBoardScale

而新的职位将是:

-mBoardScrollX + focusX * mBoardScale * factor
-mBoardScrollY + focusY * mBoardScale * factor

通过求解这两个线性方程,我得到:

mBoardScrollX = mBoardScrollX + focusX * (1 - factor) * mBoardScale;
mBoardScrollY = mBoardScrollY + focusY * (1 - factor) * mBoardScale;

然而,这不起作用!

为了消除任何错误,我甚至尝试将焦点硬编码到我的自定义视图的中间 - 并且游戏板中心在缩放时仍然摇摆:

float focusX = getWidth() / 2f;
float focusY = getHeight() / 2f;

我想我错过了一些小事,请帮助我。

我宁愿在不使用Matrix的情况下找到解决方案,因为我相信在上面的计算中缺少一些非常小的东西。是的,我已经研究了很多类似的代码,包括Chris Banes的PhotoView和Google的InteractiveChart示例。

双重更新:

pskink基于Matrix的解决方案非常有效,但是我仍然有一个错误的焦点问题 -

我试图将代码添加到the custom view,以便在双击手势上将缩放率提高100%:

public boolean onDoubleTap(final MotionEvent e) {
    float[] values = new float[9];
    mMatrix.getValues(values);
    float scale = values[Matrix.MSCALE_X];
    ValueAnimator animator = ValueAnimator.ofFloat(scale, 2f * scale);
    animator.setDuration(3000);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animator){
            float scale = (float) animator.getAnimatedValue();
            mMatrix.setScale(scale, scale, e.getX(), e.getY());
            ViewCompat.postInvalidateOnAnimation(MyView.this);
        }
    });
    animator.start();
    return true;
}

当变焦正确变焦时,焦点再次出错 - 即使焦点坐标传递给每个setScale调用。

例如,当我在屏幕中间双击时,结果将向右和向下平移太远:

emulator screenshot

android android-custom-view scaling pinchzoom image-scaling
1个回答
3
投票

使用Matrix是一个非常好的主意 - 代码更简单,你不必证明你的数学技能;-),看看如何使用Matrix#postTranslateMatrix#postScale方法:

class MyView extends View {
    private static final String TAG = "MyView";

    private final ScaleGestureDetector mScaleDetector;
    private final GestureDetector mGestureDetector;

    private final Drawable mBoard;
    private final float mBoardWidth;
    private final float mBoardHeight;
    private Matrix mMatrix;

    public MyView(Context context) {
        this(context, null, 0);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mBoard = ResourcesCompat.getDrawable(context.getResources(), R.drawable.chrome, null);
        mBoardWidth = mBoard.getIntrinsicWidth();
        mBoardHeight = mBoard.getIntrinsicHeight();
        mBoard.setBounds(0, 0, (int) mBoardWidth, (int) mBoardHeight);

        mMatrix = new Matrix();

        mScaleDetector = new ScaleGestureDetector(context, scaleListener);
        mGestureDetector = new GestureDetector(context, listener);
    }

    ScaleGestureDetector.OnScaleGestureListener scaleListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector scaleDetector) {
            float factor = scaleDetector.getScaleFactor();
            mMatrix.postScale(factor, factor, getWidth() / 2f, getHeight() / 2f);
            ViewCompat.postInvalidateOnAnimation(MyView.this);
            return true;
        }
    };

    GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY) {
            mMatrix.postTranslate(-dX, -dY);
            ViewCompat.postInvalidateOnAnimation(MyView.this);
            return true;
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        float scale = Math.max(w / mBoardWidth, h / mBoardHeight);
        mMatrix.setScale(scale, scale);
        mMatrix.postTranslate((w - scale * mBoardWidth) / 2f, (h - scale * mBoardHeight) / 2f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.concat(mMatrix);
        mBoard.draw(canvas);
        canvas.restore();
    }

    @Override
    @SuppressLint("ClickableViewAccessibility")
    public boolean onTouchEvent(MotionEvent e) {
        mGestureDetector.onTouchEvent(e);
        mScaleDetector.onTouchEvent(e);
        return true;
    }
}
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.