手写双向滑动的 ScalableImageView
程序员文章站
2022-06-23 16:39:02
12_手写双向滑动的 ScalableImageViewScalablelmageView放缩和移动GestrueDetectorGeasureDetector 的默认监听器:OnGestureListener双击监听器:OnDoubleTapListenerOverScrollerScalablelmageView放缩和移动由于有两次移动,一次是初始偏移,另一次是随手指拖动,所以要分开两次写translate()canvas.translate(offsetX * scalingFraction,...
12_手写双向滑动的 ScalableImageView
ScalablelmageView
放缩和移动
由于有两次移动,一次是初始偏移,另一次是随手指拖动,所以要分开两次写translate()
canvas.translate(offsetX * scalingFraction, offsetY * scalingFraction); //二次手动偏移
float imageScale = smallImageScale + (bigImageScale - smallImageScale) * scalingFraction;
canvas.scale(imageScale, imageScale, getWidth() / 2, getHeight() / 2); //放缩
canvas.translate(originalOffsetX, originalOffsetY); // 初始偏移
canvas.drawBitmap(bitmap, 0, 0, paint);
GestrueDetector
用于在点击和长按之外,增加其他手势的监听,例如双击、滑动。通过在View.onTouchEvent()里调用GestureDetector.onTouchEvent(),以代理的形式来实现:
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
GeasureDetector 的默认监听器:OnGestureListener
通过构造方法 GeasureDetector(Context, OnGestureListener) 来配置:
gestureDetector = new GestureDetector(context, gestureListener);
OnGestureListener的几个回调方法:
@Override
public boolean onDown(MotionEvent e) {
//每次ACTION_DOWN事件出现的时候都会被调用,在这里返回true可以保证必然消费掉 事件
return true;
}
@Override
public void onShowPress(MotionEvent e) {
//用户按下100ms不松手后会被调用,用于标记「可以显示按下状态了」
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//用户单击时被调用(长按后松手不会调用、双击的第二下时不会被调用)
return false;
}
@Override
public boolean onScroll(MotionEvent downEvent, MotionEvent event, float distanceX, float distanceY) {
//用户滑动时被调用
//第一个事件是用户按下时的ACTION_DOWN事件,第二个事件是当前事件
//偏移是按下时的位置-当前事件的位置
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//用户长按(按下500ms不松手)后会被调用
// 这个 500ms 在 GestureDetectorCompat 中变成了 600ms (???)
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//用于滑动时迅速抬起时被调用,用于用户希望控件进行惯性滑动的场景
return false;
}
双击监听器:OnDoubleTapListener
通过 GestureDetector.setOnDoubleTapListener(OnDoubleTapListener) 来配置:
gestureDetector.setOnDoubleTapListener(doubleTapListener);
OnDoubleTapListener的几个回调方法:
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//用户单击时被调用
//和onSingltTapUp()的区别在于,用户的一次点击不会立即调用这个方法,而是在一定 时间后(300ms),确认用户没有进行双击,这个方法才会被调用
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
//用户双击时被调用
//注意:第二次触摸到屏幕时就调用,而不是抬起时
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
//用户双击第二次按下时、第二次按下后移动时、第二次按下后抬起时都会被调用
//常用于「双击拖拽」的场景
return false;
}
OverScroller
用于自动计算滑动的偏移。
scroller = new OverScroller(context);
常用于onFling()方法中,调用OverScroller.fling()方法来启动惯性滑动的计算:
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//初始化滑动
scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
//下一帧刷新
postOnAnimation(flingRunner);
return false;
}
@Override
public void run() {
//计算此时的位置,并且如果滑动已经结束,就停止
if (scroller.computeScrollOffset()) {
//把此时的位置应用于界面
offsetX = scroller.getCurrX();
offsetY = scroller.getCurrY();
invalidate();
//下一帧刷新
postOnAnimation(this);
}
}
完整代码
public class ScalableImageView extends View {
private static final float IMAGE_WIDTH = Utils.dpToPixel(300);
private static final float OVER_SCALE_FACTOR = 1.5f;
Bitmap bitmap;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
float offsetX;
float offsetY;
float originalOffsetX;
float originalOffsetY;
float smallScale;
float bigScale;
boolean big;
float currentScale;
ObjectAnimator scaleAnimator;
GestureDetectorCompat detector;
HenGestureListener gestureListener = new HenGestureListener();
HenFlingRunner henFlingRunner = new HenFlingRunner();
ScaleGestureDetector scaleDetector;
HenScaleListener henScaleListener = new HenScaleListener();
OverScroller scroller;
public ScalableImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
bitmap = Utils.getAvatar(getResources(), (int) IMAGE_WIDTH);
detector = new GestureDetectorCompat(context, gestureListener);
scroller = new OverScroller(context);
scaleDetector = new ScaleGestureDetector(context, henScaleListener);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
originalOffsetX = ((float) getWidth() - bitmap.getWidth()) / 2;
originalOffsetY = ((float) getHeight() - bitmap.getHeight()) / 2;
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;
}
currentScale = smallScale;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = scaleDetector.onTouchEvent(event);
if (!scaleDetector.isInProgress()) {
result = detector.onTouchEvent(event);
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float scaleFraction = (currentScale - smallScale) / (bigScale - smallScale);
canvas.translate(offsetX * scaleFraction, offsetY * scaleFraction);
canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint);
}
private float getCurrentScale() {
return currentScale;
}
private void setCurrentScale(float currentScale) {
this.currentScale = currentScale;
invalidate();
}
private ObjectAnimator getScaleAnimator() {
if (scaleAnimator == null) {
scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0);
}
scaleAnimator.setFloatValues(smallScale, bigScale);
return scaleAnimator;
}
class HenGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent down, MotionEvent event, float distanceX, float distanceY) {
if (big) {
offsetX -= distanceX;
offsetY -= distanceY;
fixOffsets();
invalidate();
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent down, MotionEvent event, float velocityX, float velocityY) {
if (big) {
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(henFlingRunner);
}
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
big = !big;
if (big) {
offsetX = (e.getX() - getWidth() / 2f) - (e.getX() - getWidth() / 2) * bigScale / smallScale;
offsetY = (e.getY() - getHeight() / 2f) - (e.getY() - getHeight() / 2) * bigScale / smallScale;
fixOffsets();
getScaleAnimator().start();
} else {
getScaleAnimator().reverse();
}
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
}
private void fixOffsets() {
offsetX = Math.min(offsetX, (bitmap.getWidth() * bigScale - getWidth()) / 2);
offsetX = Math.max(offsetX, - (bitmap.getWidth() * bigScale - getWidth()) / 2);
offsetY = Math.min(offsetY, (bitmap.getHeight() * bigScale - getHeight()) / 2);
offsetY = Math.max(offsetY, - (bitmap.getHeight() * bigScale - getHeight()) / 2);
}
class HenFlingRunner implements Runnable {
@Override
public void run() {
if (scroller.computeScrollOffset()) {
offsetX = scroller.getCurrX();
offsetY = scroller.getCurrY();
invalidate();
postOnAnimation(this);
}
}
}
class HenScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
float initialScale;
@Override
public boolean onScale(ScaleGestureDetector detector) {
currentScale = initialScale * detector.getScaleFactor();
invalidate();
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
initialScale = currentScale;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<com.hencoder.a12_scalable.ScalableImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</com.hencoder.a12_scalable.ScalableImageView>
本文地址:https://blog.csdn.net/aha_jasper/article/details/110671566