import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ViewSwitcher;
import androidx.appcompat.app.AppCompatActivity;
public class ZoomableImageSwitcherActivity extends AppCompatActivity {
private ImageSwitcher imageSwitcher;
private GestureDetector gestureDetector;
private ScaleGestureDetector scaleGestureDetector;
private int[] imageResources = {R.drawable.image1, R.drawable.image2, R.drawable.image3};
private int currentIndex = 0;
private Matrix matrix = new Matrix();
private PointF lastPoint = new PointF();
private float scale = 1f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zoomable_image_switcher);
imageSwitcher = findViewById(R.id.imageSwitcher);
imageSwitcher.setFactory(new ViewSwitcher.ViewFactory() {
@Override
public ImageView makeView() {
return new ImageView(ZoomableImageSwitcherActivity.this);
}
});
// 初始化手势识别器
gestureDetector = new GestureDetector(this, new GestureListener());
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureListener());
// 设置手势监听器
imageSwitcher.setOnTouchListener((v, event) -> {
gestureDetector.onTouchEvent(event);
scaleGestureDetector.onTouchEvent(event);
return true;
});
// 初始显示第一张图片
setImage(currentIndex);
}
private void setImage(int index) {
imageSwitcher.setImageResource(imageResources[index]);
}
private class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
lastPoint.set(e.getX(), e.getY());
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float dx = e2.getX() - lastPoint.x;
float dy = e2.getY() - lastPoint.y;
matrix.postTranslate(-dx, -dy);
((ImageView) imageSwitcher.getCurrentView()).setImageMatrix(matrix);
lastPoint.set(e2.getX(), e2.getY());
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
// 判断左右滑动
if (velocityX > 0 && currentIndex > 0) {
// 向右滑动,显示前一张图片
currentIndex--;
setImage(currentIndex);
return true;
} else if (velocityX < 0 && currentIndex < imageResources.length - 1) {
// 向左滑动,显示后一张图片
currentIndex++;
setImage(currentIndex);
return true;
}
}
return false;
}
}
private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
scale *= scaleFactor;
scale = Math.max(0.1f, Math.min(scale, 10.0f)); // 控制缩放范围在0.1到10倍之间
matrix.setScale(scale, scale, detector.getFocusX(), detector.getFocusY());
((ImageView) imageSwitcher.getCurrentView()).setImageMatrix(matrix);
return true;
}
}
}
一、内部拦截法
getParent().requestDisallowInterceptTouchEvent(true) 请求父容器不拦截
此时父容器应该在Action_down不拦截处理
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (event.getAction = MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(ev)
return false;
}
retrun true;
}
二、外部拦截法
photoView
package com.example.myapplication.image;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
public class PhotoView extends AppCompatImageView {
private Bitmap bitmap;
private Paint paint;
// 偏移值
private float originalOffsetX;
private float originalOffsetY;
// 一边全屏,一边留白
private float smallScale;
// 一边全屏,一边超出屏幕
private float bigScale;
private float OVER_SCALE_FACTOR = 1.5f;
private float currentScale;
private boolean isEnlarge;
private GestureDetector gestureDetector;
private float offsetX;
private float offsetY;
private OverScroller overScroller;
private FlingRunner flingRunner;
private ScaleGestureDetector scaleGestureDetector;
private boolean isScale;
private int mLastX;
private int mLastY;
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对象
paint = new Paint();
gestureDetector = new GestureDetector(context, new PhotoGestureListener());
overScroller = new OverScroller(context);
flingRunner = new FlingRunner();
scaleGestureDetector = new ScaleGestureDetector(context, new PhotoScaleGestureListener());
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 这个条件由业务逻辑决定,看什么时候 子View将事件让出去
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 双指操作优先
boolean result = scaleGestureDetector.onTouchEvent(event);
if (!scaleGestureDetector.isInProgress()) {
result = gestureDetector.onTouchEvent(event);
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Drawable drawable = getDrawable();
if (drawable != null) {
initBitmap(drawable);
}
float scaleFraction = (currentScale - smallScale) / (bigScale - smallScale);
canvas.translate(offsetX * scaleFraction, offsetY * scaleFraction);
// smallScale --》 bigScale
canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
// 绘制bitmap
canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint);
}
private void initBitmap(@NonNull Drawable drawable ) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap1 = ((BitmapDrawable) drawable).getBitmap();
if (bitmap1 == null || bitmap1.equals(bitmap)) {
return;
}
bitmap = bitmap1;
Log.d("testhyy", bitmap1.toString());
// 需要得到 浮点数,否则会留条小缝
originalOffsetX = (getWidth() - bitmap.getWidth()) / 2f;
originalOffsetY = (getHeight() - bitmap.getHeight()) / 2f;
// 图片是横向的
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;
}
}
// onMeasure --> onSizeChanged
// 每次改变尺寸时也会调用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public void setImageResource(int resId) {
bitmap = BitmapFactory.decodeResource(getResources(), resId);
super.setImageResource(resId);
}
class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
// 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) (bitmap.getWidth() * bigScale - getWidth()) / 2,
(int) (bitmap.getWidth() * bigScale - getWidth()) / 2,
-(int) (bitmap.getHeight() * bigScale - getHeight()) / 2,
(int) (bitmap.getHeight() * bigScale - 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) {
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();
}
// invalidate();
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);
}
}
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, (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);
}
/**
* 属性动画,设置放大缩小的效果
*/
private ObjectAnimator scaleAnimator;
private ObjectAnimator getScaleAnimator() {
if (scaleAnimator == null) {
scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0);
}
if (isScale) {
isScale = false;
scaleAnimator.setFloatValues(smallScale, currentScale);
} else {
// 放大缩小的范围
scaleAnimator.setFloatValues(smallScale, bigScale);
}
return scaleAnimator;
}
// 属性动画,值会不断地从 smallScale 慢慢 加到 bigScale, 通过反射调用改方法
public void setCurrentScale(float currentScale) {
this.currentScale = currentScale;
invalidate();
}
class PhotoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
float initialScale;
@Override
public boolean onScale(ScaleGestureDetector detector) {
if ((currentScale > smallScale && !isEnlarge)
|| (currentScale == smallScale && isEnlarge)) {
isEnlarge = !isEnlarge;
}
currentScale = initialScale * detector.getScaleFactor();
isScale = true;
invalidate();
return false;
}
// 注意:返回true,消费事件
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
initialScale = currentScale;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
}