Порой в Android разработке бывают простые проблемы, которые не так просто решить без нужных библиотек или Custom View.
Недавно я столкнулся с проблемой создания вот такого простого эффекта:
Более подробно вы можете посмотреть запись экрана эмулятора
Простыми словами: вы можете уменьшать изображение и увеличивать его. Если вы уменьшаете изображение, а затем отпускаете его, оно возвращается в исходное состояние плавным переходом.
Казалось бы, много различных библиотек, которые могут это сделать.
Но увы, я не нашел ничего более менее подходящего
Код
Приступаем к коду:
public class ZoomableImage extends AppCompatImageView {
// Отслеживание касаний пальцев
private ScaleGestureDetector scaleDetector;
// текущий коэффициент масштабирования изображения
private float scaleFactor = 1.f;
// конструкторы
public ZoomableImage(Context context){
super(context);
init();
}
public ZoomableImage(Context context, AttributeSet set) {
super(context, set);
init();
}
public ZoomableImage(Context context, AttributeSet set, int defStyleAttr) {
super(context, set, defStyleAttr);
init();
}
// начальный коэффициент масштабирования
private final float normalScaleFactor = 1f;
// число на которое будет увеличиваться коэффициент масштабирования при анимации
private final float increasingValue = .02f;
// специальное число, которое уменьшает скорость масштабирования при уменьшении изображения
private final double decreasingDivisor = 1.032;
// минимальный коэффициент масштабирования
private final float minScaleFactor = 0.05f;
// максимальный коэффициент масштабирования
private final float maxScaleFactor = 5.0f;
// задержка для анимации
private final long delay = 4L;
// используем для выполнения анимации
final Handler handler = new Handler(Looper.getMainLooper());
// анимация возвращения к нормальному коэффициенту
// срабатывает, когда мы уменьшаем изображение и отпускаем его
final Runnable animationRunnable = new Runnable() {
@Override
public void run() {
// пока текущий коэффициент масштабирования меньше нормального
// увеличиваем его до нормального
if (scaleFactor <= normalScaleFactor) {
scaleFactor += increasingValue;
// постоянно перерисовываем наше View
invalidate();
// снова выполняем анимацию, пока условие не станет ложным
handler.postDelayed(this, delay);
} else {
// после завершения анимации, присваем текущему коэффициенту
// начальное значение и перерисовываем View
scaleFactor = normalScaleFactor;
invalidate();
}
}
};
@Override
public boolean onTouchEvent(MotionEvent ev) {
// отслеживаем касания пальцев
scaleDetector.onTouchEvent(ev);
// запускаем анимацию, если мы уменьшили изображение и отпустили его
if (ev.getAction() == MotionEvent.ACTION_UP && scaleFactor < normalScaleFactor) {
// отменяем предыдущую анимацию
handler.removeCallbacks(animationRunnable);
// запускаем новую
handler.postDelayed(animationRunnable, 4L);
}
return true;
}
private void init() {
// инициализация нашего ScaleGestureDetector'а
scaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
@Override
public void onDraw(Canvas canvas) {
canvas.save();
// масштабируем canvas вместе с нашим изображением
// Обратите внимание, что мы передаем getWidth() / 2 и getHeight() / 2
// для того, чтобы изображение масштабировалось от центра
canvas.scale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
super.onDraw(canvas);
canvas.restore();
}
// чувствительность срабатывания, которую я подобрал экспериментально
private final int sensitivity = 10;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
// если значения перемещения наших пальцев больше sensitivity то мы считаем,
// что изображение масштабируется, в противном случае - ложное срабатывание
if (Math.abs((detector.getCurrentSpan() - detector.getPreviousSpan())) > sensitivity) {
final float scale = detector.getScaleFactor();
// при уменьшении я заметил что скорость масштабирования была очень быстрая,
// поэтому я решил поделить scale на специальный коэффициент decreasingDivisor, который я тоже подобрал
// экспериментально
scaleFactor *= scale < normalScaleFactor ? scale / decreasingDivisor : scale;
// ограничиваем максимальное масштабирование в 5.0 и минимальное в 0.05
scaleFactor = Math.max(minScaleFactor, Math.min(scaleFactor, maxScaleFactor));
invalidate();
}
return true;
}
}
}
Думаю, без вопросов)
Все довольно понятно и просто.
Хорошего кода! :)
anegin
Ого. Целая статья ради ScaleGestureDetector. Так можно каждый день по статье выкладывать - документации андроида хватит на годы вперед.
KiberneticWorm Автор
Вовсе не ради ScaleGestureDetector, я хотел просто поделиться простым решением одной из частых проблем при работе с изображениями, чтобы долго не копаться и не тратить свое время на подключение лишних библиотек