12_手写双向滑动的 ScalableImageView
- ScalablelmageView
-
- 放缩和移动
- GestrueDetector
- GeasureDetector 的默认监听器:OnGestureListener
- 双击监听器:OnDoubleTapListener
- OverScroller
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://www.ctvol.com/addevelopment/890831.html