如何在自定义视图中固定捏缩放焦点? [英] How to fix pinch zoom focal point in a custom view?

查看:107
本文介绍了如何在自定义视图中固定捏缩放焦点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于我的问题,我已经准备了一个非常简单的测试应用在Github.

For my question I have prepared a very simple test app at Github.

为简单起见,我删除了浮动,滚动约束和边缘效果(在我的实际应用中效果很好)

For simplicity I have removed flinging, scroll constraints and edge effects (which actually work well in my real app):

所以并用2根手指捏住缩放(但焦点已断开!):

and pinch zooming with 2 fingers (the focus is broken though!):

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;
    }
});

最后,这里的代码绘制了被调用和偏移的游戏板Drawable:

Finally, here the code drawing the scalled and offsetted game board 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();
}

如果尝试运行我的应用程序,您会注意到在用两根手指缩放缩放手势缩放游戏板的同时,缩放焦点会跳动.

If you try running my app, you will notice that while scaling the game board with two finger pinch zoom gesture works, the zoom focal point jumps around.

我的理解是,在缩放Drawable时,我需要通过调整mBoardScrollXmBoardScrollY值来平移它-以使焦点在游戏板坐标中位于同一点.所以我的计算是-

My understanding is that while scaling the Drawable I need to pan it by adjusting the mBoardScrollX and mBoardScrollY values - so that the focus point stays at the same point in the game board coordinates. So my calculation is -

该点的位置是:

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

new 位置将位于:

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

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

By solving these 2 linear equations I get:

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

但是那行不通!

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

To eliminate any errors I have even tried hardcoding the focal point to the middle of my custom view - and still the game board center sways around while scaling:

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

我想我缺少一些小东西,请帮助我.

I think I am missing something minor, please help me.

我更愿意在不使用Matrix的情况下找到解决方案,因为我认为上述计算中确实缺少一些细微的东西.是的,我已经研究了很多可比较的代码,包括Chris Banes的 PhotoView 和<由Google提供的href ="https://android.googlesource.com/platform/development.git/+/refs/heads/master/samples/training/InteractiveChart/" rel ="nofollow noreferrer"> InteractiveChart 示例

I would prefer to find a solution without using Matrix, because I believe that something really minor is missing in the above calculations. And yes, I have already studied a lot of comparable code, including the PhotoView by Chris Banes and the InteractiveChart example by Google.

两次TAP更新:

pskink的基于Matrix的解决方案效果很好,但是我仍然遇到一个焦点错误的问题-

The Matrix-based solution by pskink works very well, however I still have one issue with a wrong focal point -

我试图将代码添加到

I have tried to add code to the custom view to increase zoom by 100% on a double tap gesture:

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调用.

And while the zoom changes correctly, the focal point is wrong again - even though the focal point coordinates are passed to each setScale call.

例如,当我在屏幕中间点按两次时,结果将左右平移太远:

For example when I double tap in the middle of the screen, the result will be panned too far right and down:

推荐答案

使用Matrix真的更好的主意-代码更加简单,您不必证明自己的数学技能; -),了解如何使用Matrix#postTranslateMatrix#postScale方法:

Using Matrix is a really better idea - the code is much more simple and you dont have to prove your math skills ;-), see how Matrix#postTranslate and Matrix#postScale methods are used:

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;
    }
}

这篇关于如何在自定义视图中固定捏缩放焦点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆