自定义 View:如何实现图片放大后拖动和滑动效果
- 2021 年 11 月 14 日
本文字数:3778 字
阅读完需:约 12 分钟
1.知识点
拖动图片的边界计算
GestureDetector.OnGestureListener.onFling 接口
OverScroller
缩小图片居中
2.原理
拖动图片的范围应该是:图片的左边小于 View 的左边,图片的右边小于 View 的右边,图片的顶边小于 View 的顶边,图片的底边小于 View 的底边
onFling:⽤于滑动时迅速抬起时被调⽤,⽤于用户希望控件进行惯性滑动的场景
OverScroller:This class encapsulates scrolling with the ability to overshoot the bounds of a scrolling operation.
由于在拖动的过程中,offsetX 和 offsetY 会被改变,导致双击缩小的时候,图片的中心发生偏移;因此,缩小动画执行的过程中,需要平滑的把偏移中心移动到原点,就能保证图片缩小后,显示在中心。使用 ObjectAnimator.addUpdateListener 来添加一个动画执行过程中的监听器,在这里进行计算;同时调用 ObjectAnimator.removeAllUpdateListeners 来清除更新监听器。
removeAllListeners:移除所有监听器,包括 AnimatorListener、AnimatorPauseListener 两类接口
removeAllUpdateListeners:移除所有监听器,包括 AnimatorUpdateListener,是属性动画特有接口
3.代码
package com.besmart.components;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.view.GestureDetectorCompat;
public class ScalableImageView extends View implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable {
public static final String TAG = "ScalableImageView";
public static final float IMAGE_WIDTH = Util.dp2px(300); // 指定图片的尺寸
public static final float OVER_SCALE_FACTOR = 1.5f;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap;
float originalOffsetX;
float originalOffsetY;
float offsetX, offsetY;
float smallScale;
float bigScale;
boolean isBig = false;
float scaleFraction = 0f;
ObjectAnimator scaleAnimator;
GestureDetectorCompat detector;
OverScroller scroller;
public void setScaleFraction(float scaleFraction) {
this.scaleFraction = scaleFraction;
invalidate();
}
public float getScaleFraction() {
return scaleFraction;
}
{
bitmap = Util.getAvatar(getResources(), (int) IMAGE_WIDTH, R.drawable.audi);
}
public ScalableImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
detector = new GestureDetectorCompat(context, this);
scroller = new OverScroller(context);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
originalOffsetX = (getWidth() - bitmap.getWidth()) / 2.0f;
originalOffsetY = (getHeight() - bitmap.getHeight()) / 2.0f;
if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) {
smallScale = (float) getWidth() / bitmap.getWidth();
bigScale = (float) getHeight() / bitmap.getHeight() * OVER_SCALE_FACTOR;
} else {
smallScale = (float) getHeight() / bitmap.getHeight();
bigScale = (float) getWidth() / bitmap.getWidth() * OVER_SCALE_FACTOR;
}
}
private ObjectAnimator getScaleAnimator() {
if (null == scaleAnimator) {
scaleAnimator = ObjectAnimator.ofFloat(this, "scaleFraction", 0, 1);
scaleAnimator.setStartDelay(0);
scaleAnimator.setDuration(500);
}
return scaleAnimator;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (null != scaleAnimator) scaleAnimator.cancel();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(offsetX, offsetY);
float scale = smallScale + (bigScale - smallScale) * scaleFraction;
canvas.scale(scale, scale, getWidth() / 2, getHeight() / 2);
canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
Log.e(TAG, "onDown: " + e);
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.e(TAG, "onShowPress: " + e);
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e(TAG, "onSingleTapUp: " + e);
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e(TAG, "onScroll: " + distanceX);
if (isBig) {
offsetX -= distanceX;
offsetX = Math.min(offsetX, (bitmap.getWidth() * bigScale - getWidth()) / 2);
offsetX = Math.max(offsetX, -(bitmap.getWidth() * bigScale - getWidth()) / 2);
offsetY -= distanceY;
offsetY = Math.min(offsetY, (bitmap.getHeight() * bigScale - getHeight()) / 2);
offsetY = Math.max(offsetY, -(bitmap.getHeight() * bigScale - getHeight()) / 2);
invalidate();
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.e(TAG, "onLongPress: " + e);
}
// 惯性滑动
@Override
public boolean onFling(MotionEvent down, MotionEvent event, float velocityX, float velocityY) {
Log.e(TAG, "onFling: " + velocityX);
if (isBig) {
scroller.fling((int) offsetX, (int) offsetY, (int) velocityX, (int) velocityY,
-(int) (bitmap.getWidth() * bigScale - getWidth()) / 2,
(int) (bitmap.getWidth() * bigScale - getWidth()) / 2,
-(int) (bitmap.getHeight() * bigScale - getHeight()) / 2,
(int) (bitmap.getHeight() * bigScale - getHeight()) / 2
);
postOnAnimation(this);
}
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.e(TAG, "onSingleTapConfirmed: " + e);
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.e(TAG, "onDoubleTap: " + e);
isBig = !isBig;
if (isBig) {
getScaleAnimator().removeAllUpdateListeners();
// getScaleAnimator().removeAllListeners();
getScaleAnimator().start();
} else {
float startOffsetX = offsetX; // 缓存开始缩小时的 图片在X轴上的偏移
float startOffsetY = offsetY; // 缓存开始缩小时的 图片在Y轴上的偏移
getScaleAnimator().removeAllUpdateListeners();
// getScaleAnimator().removeAllListeners();
getScaleAnimator().addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
offsetX = (startOffsetX) * fraction;
offsetY = (startOffsetY) * fraction;
});
getScaleAnimator().reverse();
}
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.e(TAG, "onDoubleTapEvent: " + e);
return false;
}
@Override
public void run() {
if (scroller.computeScrollOffset()) {
offsetX = scroller.getCurrX();
offsetY = scroller.getCurrY();
invalidate();
postOnAnimation(this);
}
}
}
版权声明: 本文为 InfoQ 作者【Changing Lin】的原创文章。
原文链接:【http://xie.infoq.cn/article/3c5d2e74d40bbe33ac65908f1】。文章转载请联系作者。
Changing Lin
获得机遇的手段远超于固有常规之上~ 2020.04.29 加入
我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。
评论