欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

手写双向滑动的 ScalableImageView

程序员文章站 2022-06-23 16:39:02
12_手写双向滑动的 ScalableImageViewScalablelmageView放缩和移动GestrueDetectorGeasureDetector 的默认监听器:OnGestureListener双击监听器:OnDoubleTapListenerOverScrollerScalablelmageView放缩和移动由于有两次移动,一次是初始偏移,另一次是随手指拖动,所以要分开两次写translate()canvas.translate(offsetX * scalingFraction,...

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;
}
@Overridepublic 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

相关标签: Android