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):
So the custom view in my test app only supports scrolling:
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;
}
});
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;
}
});
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.
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 -
The old position of that point is:
-mBoardScrollX + focusX * mBoardScale
-mBoardScrollY + focusY * mBoardScale
and the new position would be at:
-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;
However that does not work!
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.
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.
DOUBLE TAP UPDATE:
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;
}
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
would be really better, seeCanvas.concat
method which shows how easily you can do some nice things without that math andCanvas.scale
/Canvas.translate
/Canvas.rotate
calls, BTW nice looking ui ;-) – pskinkImageView
fromgetMainView
method to see it in action – pskinkMatrix
approach and am curious what is wrong in my simple test app. – Alexander FarbermMatrix.getValues()
? And I wonder if I should save all 9 float values in theonRestoreInstanceState
or maybe less will do? – Alexander FarberValueAnimator
is good, simply usescaleFactor = newScale / oldScale
thats it, but ok, maybe i would useObjectAnimator
- it gives less boilerplate code – pskink