文章由本人编写,版权由享学课堂所有
前言
在平时打开图库经常能看到这个效果 闲暇时光决定 模仿了一下写了这个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找到对应边界,如下图所示。
手指可滑动的范围是黑色框以外的部分。
上边说到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)来处理图片放大后滑动。