Android知识总结
一、GestureDetector单指点击手势
GestureDetector的工作原理是,当我们接收到用户触摸消息时,将这个消息交给GestureDetector去加工,我们通过设置侦听器获得GestureDetector处理后的手势。
GestureDetector提供了两个侦听器接口,OnGestureListener处理单击类消息,OnDoubleTapListener处理双击类消息。
-
OnGestureListener
的接口有这几个:
// 单击,触摸屏按下时立刻触发
abstract boolean onDown(MotionEvent e);
// 单击抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势)
//单击抬起时触发,且只在双击的第一次抬起时触发。(连续点击三次,则会触发两次)
abstract boolean onSingleTapUp(MotionEvent e);
// 短按(触摸反馈),触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会
//它是在 View 被点击(按下)时调用,其作用是给用户一个视觉反馈,让用户知道我这个控件被点击了,
//这样的效果我们也可以用 Material design 的 ripple 实现,或者直接 drawable 写个背景也行。
//它是一种延时回调,延迟时间是 100 ms。也就是说用户手指按下后,如果立即抬起或者事件立即被拦截,
//时间没有超过 100 ms的话,这条消息会被 remove 掉,也就不会触发这个回调。
abstract void onShowPress(MotionEvent e);
// 长按,触摸屏按下后既不抬起也不移动,过一段时间后触发
abstract void onLongPress(MotionEvent e);
// 滚动,触摸屏按下后移动
// e1:手指按下时的 MotionEvent
// e2:手指当前的 MotionEvent
// distanceX:在X轴上划过的距离 --- 旧位置 减去 新位置
// distanceY:在Y轴上划过的距离
abstract boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//处理惯性滑动。最小滑动速度50dip/s(dp=dip)。最大8000dp/s。
// 抛掷(惯性滑动)
// e1:手指按下时的 MotionEvent,可以知道按下位置等
// e2:手指当前的 MotionEvent
// velocityX:在X轴上的运动速度(像素/秒)
// velocityY:在Y轴上的运动速度(像素/秒)
abstract boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
-
OnDoubleTapListener
的接口有这几个:
300ms内点击两次才算双击。
// 双击,手指在触摸屏上迅速点击第二下时触发
abstract boolean onDoubleTap(MotionEvent e);
// 在双击手势中发生事件时通知,包括按下、移动和抬起事件
//双击第二次点击时的按下,移动和抬起事件都会回调。
abstract boolean onDoubleTapEvent(MotionEvent e);
// 单击确认,即很快的按下并抬起,但并不连续点击第二下
abstract boolean onSingleTapConfirmed(MotionEvent e);
有时候我们并不需要处理上面所有手势,方便起见,Android提供了另外一个类SimpleOnGestureListener,实现了OnGestureListener
, OnDoubleTapListener
, OnContextClickListener
这三个接口,并重写了接口的方法。所以我们可以 new 一个 SimpleOnGestureListener
对象,这样就不用重写接口的所有方法,而只写自己需要的方法即可
1.1、双击缩放
// 创建 GestureDetector对象
gestureDetector = new GestureDetector(context, new PhotoGestureListener());
class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
// 必须为true才表示消费事件
@Override
public boolean onDown(MotionEvent e) {
return true;
}
// 双击处理缩放
@Override
public boolean onDoubleTap(MotionEvent e) {
isEnlarge = !isEnlarge;
if (isEnlarge) {
currentScale = bigScale;
} else {
currentScale = smallScale;
}
invalidate();
return super.onDoubleTap(e);
}
}
//必须重写onTouchEvent,因为GestureDetector里面自己重写了事件处理。
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
1.2、缩放效果
public boolean onDoubleTap(MotionEvent e) {
isEnlarge = !isEnlarge;
if (isEnlarge) {
getScaleAnimator().start();
} else {
getScaleAnimator().reverse();
}
return super.onDoubleTap(e);
}
private ObjectAnimator getScaleAnimator() {
if (scaleAnimator == null) {
// values必须要有,否则运行时报错
scaleAnimator = ObjectAnimator.ofFloat(this,
"currentScale", 0);
}
scaleAnimator.setFloatValues(smallScale, bigScale);
return scaleAnimator;
}
public float getCurrentScale() {
return currentScale;
}
public void setCurrentScale(float currentScale) {
this.currentScale = currentScale;
// 属性动画变化是刷新界面
invalidate();
}
1.3、手指滑动
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isEnlarge) {
offsetX -= distanceX;
offsetY -= distanceY;
// 处理边界问题
fixOffsets();
invalidate();
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
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);
}
1.4、惯性滑动
public boolean onFling(MotionEvent down, MotionEvent event, float velocityX, float velocityY) {
if (isEnlarge) {
overScroller.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(flingRunner);
}
return false;
}
class FlingRunner implements Runnable {
@Override
public void run() {
// 动画还在执行,返回true
if (overScroller.computeScrollOffset()) {
offsetX = overScroller.getCurrX();
offsetY = overScroller.getCurrY();
invalidate();
postOnAnimation(this);
}
}
}
二、ScaleGestureDetector 双指点击手势
//缩放时。返回值代表本次缩放事件是否已被处理。如果已被处理,那么detector就会重置缩
//放事件;如果未被处理,detector会继续进行计算,修改getScaleFactor()的返回值,直到被
//处理为止。因此,它常用在判断只有缩放值达到一定数值时才进行缩放。
public boolean onScale(ScaleGestureDetector detector);
// 缩放开始。该detector是否处理后继的缩放事件。返回false时,不会执行onScale()。
public boolean onScaleBegin(ScaleGestureDetector detector);
//缩放结束时
public void onScaleEnd(ScaleGestureDetector detector);
2.1、实现
scaleGestureListener = new PhotoScaleGestureListener();
scaleGestureDetector = new ScaleGestureDetector(context, scaleGestureListener);
public boolean onTouchEvent(MotionEvent event) {
// 双指缩放操作优先处理事件
boolean result = scaleGestureDetector.onTouchEvent(event);
// 如果不是双指缩放才处理手势事件
if (!scaleGestureDetector.isInProgress()) {
result = gestureDetector.onTouchEvent(event);
}
return result;
}
class PhotoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
float initialScale;
// 缩放中回调 -- 倍数,焦点
@Override
public boolean onScale(ScaleGestureDetector detector) {
// getScaleFactor:将比例因子从上一个缩放事件返回到当前事件
currentScale = initialScale * detector.getScaleFactor();
invalidate();
return false;
}
// 缩放前回调,返回true 消费这个缩放事件
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
initialScale = currentScale;
return true;
}
// 缩放后回调
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
三、实例代码
实现 图片的放大、缩小、平移、惯性处理和单双指操作。
public class PhotoView extends View {
private Bitmap mBitmap;
private Paint mPaint;
// 偏移值
private float mOriginalOffsetX;
private float mOriginalOffsetY;
// 一边全屏,一边留白
private float mSmallScale;
// 一边全屏,一边超出屏幕
private float mBigScale;
private float OVER_SCALE_FACTOR = 1.5f;
private float mCurrentScale;
private FlingRunner flingRunner;
//用来实现抛出后的回弹效果
private OverScroller overScroller;
//是否放大
private boolean isEnlarge;
//偏移量
private float offsetX;
private float offsetY;
//判断是否进行了双指缩放
private boolean isScale;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
public PhotoView(Context context) {
this(context, null);
}
public PhotoView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
// 获取bitmap对象
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.photo);
mPaint = new Paint();
mGestureDetector = new GestureDetector(context, new PhotoGestureListener());
overScroller = new OverScroller(context);
flingRunner = new FlingRunner();
mScaleGestureDetector = new ScaleGestureDetector(context, new PhotoScaleGestureListener());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 双指操作优先
boolean result = mScaleGestureDetector.onTouchEvent(event);
if (!mScaleGestureDetector.isInProgress()) {
result = mGestureDetector.onTouchEvent(event);
}
return result;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 需要得到 浮点数,否则会留条小缝
mOriginalOffsetX = (getWidth() - mBitmap.getWidth()) / 2f;
mOriginalOffsetY = (getHeight() - mBitmap.getHeight()) / 2f;
if ((float) mBitmap.getWidth() / mBitmap.getHeight() > (float) getWidth() / getHeight()) {
// 图片是横向的
mSmallScale = (float) getWidth() / mBitmap.getWidth();
mBigScale = (float) getHeight() / mBitmap.getHeight();
} else {
// 纵向的图片
mSmallScale = (float) getHeight() / mBitmap.getHeight();
mBigScale = (float) getWidth() / mBitmap.getWidth();
}
mCurrentScale = mSmallScale;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//TODO 通过系数解决,放大小小后图片位置仍然居中
float scaleFraction = (mCurrentScale - mSmallScale) / (mBigScale - mSmallScale);
//平移处理
canvas.translate(offsetX * scaleFraction, offsetY * scaleFraction);
// smallScale --》 bigScale (实际上是缩放坐标系)
canvas.scale(mCurrentScale, mCurrentScale, getWidth() / 2f, getHeight() / 2f);
// 绘制bitmap
canvas.drawBitmap(mBitmap, mOriginalOffsetX, mOriginalOffsetY, mPaint);
}
class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
public PhotoGestureListener() {
super();
}
// Up时触发 双击的时候,触发两次??第二次抬起时触发
@Override
public boolean onSingleTapUp(MotionEvent e) {
return super.onSingleTapUp(e);
}
// 长按 -- 300ms
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
/**
* 类似move事件
*
* @param e1
* @param e2
* @param distanceX 在 X 轴上滑过的距离(单位时间) 旧位置 - 新位置
* @param distanceY
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 只有在放大的情况下,才能进行移动
if (isEnlarge) {
offsetX -= distanceX;
offsetY -= distanceY;
fixOffsets();
invalidate();
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
// 抛掷
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isEnlarge) {
// 只会处理一次,抛出后的回弹效果实现
overScroller.fling((int) offsetX, (int) offsetY, (int) velocityX, (int) velocityY,
-(int) (mBitmap.getWidth() * mBigScale - getWidth()) / 2,
(int) (mBitmap.getWidth() * mBigScale - getWidth()) / 2,
-(int) (mBitmap.getHeight() * mBigScale - getHeight()) / 2,
(int) (mBitmap.getHeight() * mBigScale - getHeight()) / 2, 600, 600);
postOnAnimation(flingRunner); //启动动画事务
}
return super.onFling(e1, e2, velocityX, velocityY);
}
// 延时触发 100ms -- 点击效果,水波纹
@Override
public void onShowPress(MotionEvent e) {
super.onShowPress(e);
}
// 按下 -- 注意:直接返回true
@Override
public boolean onDown(MotionEvent e) {
return true;
}
// 双击 -- 第二次点击按下的时候 -- 40ms(小于表示:防抖动) -- 300ms
@Override
public boolean onDoubleTap(MotionEvent e) {
isEnlarge = !isEnlarge;
if (isEnlarge) {
//TODO 解决,以手指点击的位置放大缩小问题
offsetX = (e.getX() - getWidth() / 2f) - (e.getX() - getWidth() / 2f) * mBigScale / mSmallScale;
offsetY = (e.getY() - getHeight() / 2f) - (e.getY() - getHeight() / 2f) * mBigScale / mSmallScale;
fixOffsets();
// 启动属性动画
getScaleAnimator().start();
} else {
getScaleAnimator().reverse();
}
return super.onDoubleTap(e);
}
// 双击第二次 down、move、up都会触发
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return super.onDoubleTapEvent(e);
}
// 单击按下时触发,双击时不触发,
// 延时300ms触发TAP事件
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onContextClick(MotionEvent e) {
return super.onContextClick(e);
}
}
//实现一直循环执行动画
class FlingRunner implements Runnable {
@Override
public void run() {
// 动画还在执行 则返回true
if (overScroller.computeScrollOffset()) {
//拿到当前偏移量,进行刷新
offsetX = overScroller.getCurrX();
offsetY = overScroller.getCurrY();
invalidate();
// 没帧动画执行一次,性能更好
postOnAnimation(this);
}
}
}
private void fixOffsets() {
offsetX = Math.min(offsetX, (mBitmap.getWidth() * mBigScale - getWidth()) / 2);
offsetX = Math.max(offsetX, -(mBitmap.getWidth() * mBigScale - getWidth()) / 2);
offsetY = Math.min(offsetY, (mBitmap.getHeight() * mBigScale - getHeight()) / 2);
offsetY = Math.max(offsetY, -(mBitmap.getHeight() * mBigScale - getHeight()) / 2);
}
/**
* 属性动画,设置放大缩小的效果
*/
private ObjectAnimator mScaleAnimator;
private ObjectAnimator getScaleAnimator() {
if (mScaleAnimator == null) {
mScaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0);
}
//TODO 解决双指缩放后,点击放大缩小时的动画闪烁问题
if (isScale) {
isScale = false;
mScaleAnimator.setFloatValues(mSmallScale, mCurrentScale);
} else {
//放大缩小的范围
mScaleAnimator.setFloatValues(mSmallScale, mBigScale);
}
return mScaleAnimator;
}
// 属性动画,值会不断地从 smallScale 慢慢 加到 bigScale, 通过反射调用改方法
public void setCurrentScale(float currentScale) {
mCurrentScale = currentScale;
invalidate();
}
//实现双指放大和缩小功能
class PhotoScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
float initialScale;
@Override
public boolean onScale(ScaleGestureDetector detector) {
if ((mCurrentScale > mSmallScale && !isEnlarge)
|| (mCurrentScale == mSmallScale && isEnlarge)) {
isEnlarge = !isEnlarge;
}
//detector.getScaleFactor() 获取缩放因子
mCurrentScale = initialScale * detector.getScaleFactor();
isScale = true;
invalidate();
return false;
}
// 注意:返回true,消费事件
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
initialScale = mCurrentScale;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
}
}
}
- OverScroller 里的 fling 方法参数讲解
/**
*
* @param startX x轴,起始位置
* @param startY Y轴,起始位置
* @param velocityX x轴,速度(可以用 onFling 中的系统速度)
* @param velocityY Y轴,速度(可以用 onFling 中的系统速度)
* @param minX x最小限制条件
* @param maxX x最大限制条件
* @param minY y最小限制条件
* @param maxY y最大限制条件
* @param overX x轴,允许的最大滑动距离(就是可留白的大小)
* @param overY Y轴,允许的最大滑动距离(就是可留白的大小)
*/
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY, int overX, int overY) {
}