实现双向滑动BidirectionalImageView

文章由本人编写,版权由享学课堂所有

前言

GIF.gif

在平时打开图库经常能看到这个效果 闲暇时光决定 模仿了一下写了这个demo, 下边就是效果图。这个效果主要利用GestureDetectorCompat处理手势 然后通过OverScroller实现。

正文大纲

  • GestureDetectorCompat使用
  • 放大以及平移功能的实现
    • 放大功能实现
    • 平移功能实现
    • 平滑过程的实现
  • OverScroller实现快速滑动效果
    • OverScroller及其滑动范围
    • post和postOnAnimation区别
    • OverScroller和Scroller区别
  • 总结

正文

GestureDetectorCompat使用

首先看初始化创建对象

detector = new GestureDetectorCompat(context, this);

然后我们利用GestureDetectorCompat 拦截当前的的事件

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return detector.onTouchEvent(event);
    }

处理好这些,GestureDetectorCompat可实现的回调就可以响应了。

       @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 e1, MotionEvent e2, float distanceX, float distanceY) {
         
        }
        @Override
        public void onLongPress(MotionEvent e) {

        }
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }


        @Override
        public boolean onDoubleTap(MotionEvent e) {

        }

这时候我们会有两类疑问,这些相似的响应的方法是什么?他们和我们自带的那些onclick有什么区别?下边简单介绍一下

 public boolean onDown(MotionEvent e);

onDown是我们收到ACTION_DOWN时候就会被调用,一般来说,通过该事件处理我当前事件是否被消费了,然后返回的值通过detector.onTouchEvent(event)返回体现出来。

 public void onShowPress(MotionEvent e);

onShowPress是一个预按下的事件,在GestureDetector中是响应到ACTION_DOWN事件,100毫秒之后触发的。

 public boolean onSingleTapUp(MotionEvent e);

onSingleTapUp很好理解 每次点击抬起时调用。这里要注意,如果长按事件被执行那么onSingleTapUp就不执行。

 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);

onScroll 类似ACTION_MOVE手指发生移动之后调用,e1表示按下时候的时间,e2表示正在处理的事件,distanceX和distanceY表示移动距离。

 public void onLongPress(MotionEvent e);

onLongPress是长按时调用的默认事件是600毫秒。可以通过detector.setIsLongpressEnabled(false);将长按事件取消。

 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

onFling是手指快速滑动时调用的,下边我会介绍。

 public boolean onDoubleTap(MotionEvent e);
 public boolean onDoubleTapEvent(MotionEvent e);

onDoubleTap和onDoubleTapEvent都是双击点击事件,区别在于onDoubleTapEvent第二次点击可以滑动。


放大以及平移功能的实现

放大功能实现
    @Override
    public boolean onDoubleTap(MotionEvent e) {
            isEnlarge = !isEnlarge;
            scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0);
            if (isEnlarge) {
                  scaleAnimator.start();
            } else {
                  scaleAnimator.reverse();
            }   
            ....
        }
        
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ....
        canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
    }

onDoubleTap 我利用onDoubleTap双击事件 控制isEnlarge操作动画来实现图片的放大和缩小的效果。
接下来我们去看一下放到后如何进行移动的。

平移功能实现
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ....
        canvas.translate(motionEventX * currentScale, motionEventY * currentScale);
        ....
    }

这个也很简单,通过canvas.translate 改变坐标系,然后随着手指对应图片的滑动的。那么motionEventX和motionEventY是如何计算出来的呢?
这里 我们是通过GestureDetectorCompat中的onScroll这个方法计算出来的,为什么会有两个MotionEvent参数,下边简单叙述一下对应的参数的作用。

平滑过程的实现
 public boolean onScroll(MotionEvent down, MotionEvent event, float distanceX, float distanceY);

MotionEvent down 代表按下的事件,MotionEvent event代表当前的事件,distanceX代表上一个点到当前点的x轴距离,distanceY代表上一个点到当前点的Y轴距离。

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (isEnlarge) {
                motionEventX -= distanceX;
                motionEventY -= distanceY;
                ....
                invalidate();
            }
            return false;
        }

上边是onScroll的具体实现,motionEventX和motionEventY是通过distanceX和distanceY计算出来的,但是这里为什么要用减法呢?手指向右移动图片,distanceX是正值,canvas.translate的坐标也要是负值图片才能要向右移动。同理distanceY也是如此。
这里要注意motionEventX和motionEventY的不要超过屏幕的边距。


OverScroller实现快速滑动效果

OverScroller及其滑动范围

运行了之后,我发现快滑的时候没有效果,这是因为我们没有对onFling进行处理,我们可以利用OverScroller中的scroller.fling方法进行计算,处理快速滑动的事件。

  private OverScroller scroller;
 
  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
     if (isEnlarge) {
         scroller.fling((int) motionEventX, (int) motionEventY, (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(motionRunnable);
            }
            return false;
        }

scroller.fling的参数有点多 但是很好记。先看下八个参数是什么。

   public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY);

startX和startY代表初始的位置,在这个demo中就是我们手指点击的位置motionEventX,和motionEventY。velocityX和velocityY代表速度,手指抬起的时和之前的位移除以时间的计算,得出的结果。
minX maxX minY maxY处理边距,这里已中心点为原点,放大后超过图片大小的部分除以2找到对应边界,如下图所示。

OverScroller 边距.jpg

手指可滑动的范围是黑色框以外的部分。
上边说到scroller.fling是对于滑动方法的计算,但是我们需要对当前布局进行刷新。这里就用到了一个方法scroller.computeScrollOffset()判断是否完成滚动从而进行刷新。这里可以看一下postOnAnimation(motionRunnable);方法。

MotionRunner motionRunnable = new MotionRunner();
....
postOnAnimation(motionRunnable);
    class MotionRunner implements Runnable {

        @Override
        public void run() {
            if (scroller.computeScrollOffset()) {
                motionEventX = scroller.getCurrX();
                motionEventY = scroller.getCurrY();
                invalidate();
                postOnAnimation(this);
            }
        }
    }

这样我们每一帧动画执行完成后,不断调用postOnAnimation,直到scroller滚动完成。

post和postOnAnimation区别

这里postOnAnimation和post区别在于postOnAnimation在下一帧之后执行而post是立即在主线程中执行,有可能post执行要比postOnAnimation快,或者一帧中post会执行多次。这里使用ViewCompat.postOnAnimation兼容性更好。

OverScroller和Scroller区别

在这个demo中我用的是OverScroller,但是他与Scroller有什么区别呢?
简单说下overscrller会多两个参数

public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY, int overX, int overY);

overX和overY代表超过范围的位置,在原来基础的距离上增加overx和overy。

总结

这里我们利用GestureDetectorCompat处理手势操作,处理双击事件以及滑动事件。双击事件执行后通过动画和canvas.scale()实现了放大缩小操作,通过滑动事件(onFling和onScroll)来处理图片放大后滑动。

你可能感兴趣的:(实现双向滑动BidirectionalImageView)