Для моего вопроса я подготовил очень простое тестовое приложение на Github.
Для простоты я удалил бросание, ограничения прокрутки и эффекты краев (которые на самом деле хорошо работают в моем реальном приложении):
Итак пользовательское представление в моем тестовом приложении поддерживает только прокрутку:
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;
}
});
Наконец, вот код, рисующий масштабированное и смещенное игровое поле 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
мне нужно панорамироватьнастраивая значения mBoardScrollX
и mBoardScrollY
, чтобы точка фокусировки оставалась в той же точке в координатах игрового поля.Итак, мой расчет -
Позиция old этой точки:
-mBoardScrollX + focusX * mBoardScale
-mBoardScrollY + focusY * mBoardScale
, а позиция new будет в:
-mBoardScrollX + focusX * mBoardScale * factor
-mBoardScrollY + focusY * mBoardScale * factor
Решая эти 2 линейных уравнения, я получаю:
mBoardScrollX = mBoardScrollX + focusX * (1 - factor) * mBoardScale;
mBoardScrollY = mBoardScrollY + focusY * (1 - factor) * mBoardScale;
Однако это не работает!
Чтобы устранить любые ошибки, я даже пытался жестко кодировать фокуссередина моего собственного представления - и все же центр игрового поля покачивается при масштабировании:
float focusX = getWidth() / 2f;
float focusY = getHeight() / 2f;
Я думаю, что упускаю что-то незначительное, пожалуйста, помогите мне.
Я бы предпочел найтирешение без использования Matrix
, потому что я считаю, что что-то действительно незначительное отсутствует в приведенных выше расчетах.И да, я уже изучил много сравнимого кода, включая PhotoView Криса Бейнса и пример InteractiveChart от Google.
DOUBLE TAP UPDATE:
Решение на основе Matrix
от pskink работает очень хорошо, однако у меня все еще есть одна проблема с неправильным фокусом -
Я пытался добавить код к пользовательский вид для увеличения зума на 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
вызов.
Например, когда я дважды нажимаю на середину экрана, результат будет панорамироваться слишком далеко вправо и вниз: