欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android - 自定义可手势移动、放缩ImageView

程序员文章站 2024-03-24 14:52:10
...

又很长的一段时间没写博客,趁着空闲之余,做一下一个常用的自定义View的记录。

手势移动、放缩ImageView是一个很常用的控件,之前都是用别人写好的,发现对Matrix类不是很熟悉,估计以后可能会经常用到这个类,所以想深入了解一下Matrix原理和怎么使用!

Matrix

网上太多资料了,比如 Android Matrix矩阵,大家自行查资料,只有理解了Matrix变换矩阵,才能看懂下面的代码。

实现手势移动、放缩ImageView

直接上码:

package com.johan.fh.camera.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;

/**
 * Created by johan on 2019/6/22.
 */

public class GestureImageView extends ImageView {

    private static final int MODE_NONE = 0;
    private static final int MODE_MOVE = 1;
    private static final int MODE_SCALE = 2;

    /** 上次变换矩阵 */
    private Matrix lastMatrix;
    /** 当前变换矩阵 */
    private Matrix useMatrix;
    /** 矩阵变化后Drawable坐标 */
    private RectF rect;
    /** 手势模式 单指滑动 双指放缩 */
    private int mode;
    /** 第一只手指开始坐标 */
    private float startX, startY;
    /** 第二只手指坐标 */
    private float start2X, start2Y;
    /** 两指中心坐标 作为放缩的中心点 */
    private float centerX, centerY;
    /** 两指距离 */
    private float startDistance;

    private boolean animate;

    public GestureImageView(Context context) {
        super(context);
    }

    public GestureImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setScaleType(ScaleType.MATRIX);
        lastMatrix = new Matrix();
        useMatrix = new Matrix();
        rect = new RectF();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	if (animate) return false;
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN :
                actionDown(event);
                break;
            case MotionEvent.ACTION_POINTER_DOWN :
                actionPointerDown(event);
                break;
            case MotionEvent.ACTION_MOVE :
                actionMove(event);
                break;
            case MotionEvent.ACTION_POINTER_UP :
                actionPointerUp(event);
                break;
            case MotionEvent.ACTION_UP :
                actionUp(event);
                break;
        }
        return true;
    }

    /**
     * 单指Down事件
     * @param event
     */
    private void actionDown(MotionEvent event) {
        mode = MODE_MOVE;
        startX = event.getX();
        startY = event.getY();
        lastMatrix.set(getImageMatrix());
    }

    /**
     * 多指Down事件
     * @param event
     */
    private void actionPointerDown(MotionEvent event) {
        mode = MODE_SCALE;
        startX = event.getX(0);
        startY = event.getY(0);
        start2X = event.getX(1);
        start2Y = event.getY(1);
        centerX = startX + (start2X - startX) / 2;
        centerY = startY + (start2Y - startY) / 2;
        startDistance = calculateDistance(startX, startY, start2X, start2Y);
        lastMatrix.set(getImageMatrix());
    }

    /**
     * Move事件
     * @param event
     */
    private void actionMove(MotionEvent event) {
        if (mode == MODE_MOVE) {
            float moveX = event.getX() - startX;
            float moveY = event.getY() - startY;
            // 由于moveX和moveY是居于点击开始的距离
            // 所以当前变换矩阵要重置为Down事件开始的变化矩阵
            useMatrix.set(lastMatrix);
            // 移动距离
            useMatrix.postTranslate(moveX, moveY);
            // 计算将要变换矩阵左边
            matrixRect(useMatrix);
            // 边界检测
            if (rect.left > 0 || rect.top > 0 || rect.right < getWidth() || rect.bottom < getHeight()) {
                useMatrix.set(getImageMatrix());
                return;
            }
            // Image变换
            setImageMatrix(useMatrix);
        } else if (mode == MODE_SCALE) {
            float currentX = event.getX(0);
            float currentY = event.getY(0);
            float current2X = event.getX(1);
            float current2Y = event.getY(1);
            float distance = calculateDistance(currentX, currentY, current2X, current2Y);
            // 由于distance是居于点击开始的距离
            // 所以当前变换矩阵要重置为Down事件开始的变化矩阵
            useMatrix.set(lastMatrix);
            // 放缩
            useMatrix.postScale(distance / startDistance, distance / startDistance, centerX, centerY);
            // 计算当前放缩倍数
            float scale = getScale(useMatrix);
            // 倍数不能小于1而不能大于2
            if (scale < 1 || scale > 2) {
                useMatrix.set(getImageMatrix());
                return;
            }
            // Image变换
            setImageMatrix(useMatrix);
        }
    }

    /**
     * 多指Up事件
     * @param event
     */
    private void actionPointerUp(MotionEvent event) {
        // 放缩之后可能边界越界 需要调整
        offset();
        mode = MODE_NONE;
    }

    /**
     * 单指Up事件
     * @param event
     */
    private void actionUp(MotionEvent event) {
        mode = MODE_NONE;
    }

    /**
     * 计算两指距离
     * @param point1X
     * @param point1Y
     * @param point2X
     * @param point2Y
     * @return
     */
    private float calculateDistance(float point1X, float point1Y, float point2X, float point2Y) {
        return (float) Math.sqrt((point2X - point1X) * (point2X - point1X) + (point2Y - point1Y) * (point2Y - point1Y));
    }

    /**
     * 变换矩阵 获取矩阵变化后的坐标
     * 由于Move事件比较频繁 如果在方法内创建对象的话 会产生很多垃圾 虽然不大 能避免就避免吧
     * @param matrix
     */
    private void matrixRect(Matrix matrix) {
        if (getDrawable() == null) return;
        int drawableWidth = getDrawable().getIntrinsicWidth();
        int drawableHeight = getDrawable().getIntrinsicHeight();
        rect.set(0, 0, drawableWidth, drawableHeight);
        // 这样可以获取矩阵的坐标信息 有用
        matrix.mapRect(rect);
    }

    /**
     * 获取矩阵缩放倍数
     * @param matrix
     * @return
     */
    private float getScale(Matrix matrix) {
        // 放缩倍数存放于参数 不用计算便可取出来
        float[] values = new float[9];
        // getValue取出矩阵所有的值
        matrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }

    /**
     * 放缩之后 计算调整
     */
    private void offset() {
        matrixRect(useMatrix);
        float offsetLeft = 0, offsetTop = 0;
        if (rect.left > 0) {
            offsetLeft = -rect.left;
        }
        if (rect.right < getWidth()) {
            offsetLeft = getWidth() - rect.right;
        }
        if (rect.top > 0) {
            offsetTop = -rect.top;
        }
        if (rect.bottom < getHeight()) {
            offsetTop = getHeight() - rect.bottom;
        }
        if (offsetLeft != 0 || offsetTop != 0) {
            offsetAnimation(offsetLeft, offsetTop);
        }
    }

    /**
     * 调整动画
     * @param offsetLeft
     * @param offsetTop
     */
    private void offsetAnimation(float offsetLeft, float offsetTop) {
        int duration = 400;
        final float stepLeft = offsetLeft / duration;
        final float stepTop = offsetTop / duration;
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, duration);
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                lastMatrix.set(getImageMatrix());
                animate = true;
            }
            @Override
            public void onAnimationEnd(Animator animation) {
                animate = false;
            }
        });
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 注意:上面onAnimationStart方法可能在这个方法之后才调用 判断一下
                if (!animate) return;
                // 与Move事件原理一样
                useMatrix.set(lastMatrix);
                // 计算移动
                float time = (float) animation.getAnimatedValue();
                float left = stepLeft * time;
                float top = stepTop * time;
                // 矩阵移动
                useMatrix.postTranslate(left, top);
                // Image变换
                setImageMatrix(useMatrix);
            }
        });
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }

    @Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        super.setImageDrawable(drawable);
        reset();
    }

    @Override
    public void setImageResource(@DrawableRes int resId) {
        super.setImageResource(resId);
        reset();
    }

    /**
     * 重新设置Drawable 需要手动重置矩阵
     */
    private void reset() {
        lastMatrix.reset();
        setImageMatrix(lastMatrix);
    }

}

这里我们需要注意的是定义了2个Matrix,lastMatrix矩阵主要是记录点击前的ImageMatrix,useMatrix使用之前,都要set回到点击前的变换矩阵,因为move事件计算的数值都是点击开始的,之后才能正常使用!

至于其他的吧,代码已经有详细的注释了,相信聪明的你看得懂!!

最好能自己动手试试,毕竟纸上得来终觉浅!