一步步实现贴纸效果

现在很多的APP,尤其是对于图片处理的APP,都有关于拍照完之后添加水印的功能,我之前是在大名鼎鼎的Keep上看到了这种效果,当时就觉得体验非常不错。后来过了好长时间之后,回过头来再次思考这个问题的时候发现其实就是通过操作图片的bitmap最后生成合成的图片的业务逻辑。当然,我们在本篇中不讨论如何整合bitmap,我们的重点在于实现这种类似贴纸的效果。在上篇博客中,我已经分享了关于多点触控操作图片的技术分析,在本篇博客中,也会用到这些技术。如果有不明白的朋友,可以参考一步步教你实现图片放大、平移、旋转这篇文章。

大家先看看图片效果:


一步步实现贴纸效果_第1张图片
image.jpg

(我去,图片这么大!)

功能列表

通过图片,我来说一下我们要做哪些事情:

  1. 触摸右上角的图片,实现单指操作图片旋转。
  2. 触摸左下角的图片,实现单指操作图片缩放。
  3. 触摸照片,实现单指平移图片功能。
  4. 双指触摸照片,实现多点操作图片缩放。

实现步骤

根据制定的功能,接下来我来说说要实现的哪些东西:

1. Matrix

我们需要利用Matrix去操作矩阵的变换,通过变换矩阵来最终改变图片。我声明了三个变量:

    // 绘制图片的矩阵
    private Matrix matrix;
    // 手指按下时图片的矩阵
    private Matrix downMatrix = new Matrix();
    // 手指移动时图片的矩阵
    private Matrix moveMatrix = new Matrix();

2. Bitmap

我们要操作的是Bitmap,因此我们需要声明页面上图片的bitmap变量:

    // 资源图片的位图
    private Bitmap srcImage;
    // 资源缩放图片的位图和矩形区域
    private Bitmap srcImageResize;
    private Rect rectResize;
    // 资源旋转图片的位图和矩形区域
    private Bitmap srcImageRotate;
    private Rect rectRotate;

3. PointF

由于我们需要根据一个参照点去完成缩放,旋转的操作,因此我们需要去声明两个中心点:

    // 多点触屏时的中心点
    private PointF midPoint = new PointF();
    // 图片的中心点坐标
    private PointF imageMidPoint = new PointF();

在这里,需要指出,midPoint是手指间触屏后的中心点,而imageMidPoint是为了完成单指旋转和缩放的参考中心点。

4. Action Mode

我们要根据不同触摸事件完成不同的操作,因此需要定义一个操作模式:

    // 触控模式
    private int mode;
    private static final int NONE = 0; // 无模式
    private static final int TRANS = 1; // 拖拽模式
    private static final int ROTATE = 2; // 单点旋转模式
    private static final int ZOOM_SINGLE = 3; // 单点缩放模式
    private static final int ZOOM_MULTI = 4; // 多点缩放模式

5. onTouchEvent()

我们的操作核心逻辑都在这个方法中。在上一节,我们已经详细分析过。

6. invalidate()

最终完成矩阵变换之后,我们得到最终的绘图矩阵,来回调onDraw()。

核心代码onTouchEvent()

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                // 旋转手势验证
                if (isInActionCheck(event, rectRotate)) {
                    mode = ROTATE;
                    imageMidPoint = getImageMidPoint();
                    oldRotation = getSpaceRotation(event);
                    downMatrix.set(matrix);
                    Log.d("onTouchEvent", "旋转手势");
                }
                // 单点缩放手势验证
                else if (isInActionCheck(event, rectResize)) {
                    mode = ZOOM_SINGLE;
                    imageMidPoint = getImageMidPoint();
                    oldDistance = getSingleTouchDistance(event);
                    downMatrix.set(matrix);
                    Log.d("onTouchEvent", "单点缩放手势");
                }
                // 平移手势验证
                else if (isTranslationActionCheck(srcImage, downX, downY)) {
                    mode = TRANS;
                    downMatrix.set(matrix);
                    Log.d("onTouchEvent", "平移手势");
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN: // 多点触控
                mode = ZOOM_MULTI;
                oldDistance = getMultiTouchDistance(event);
                midPoint = getMidPoint(event);
                downMatrix.set(matrix);
                break;
            case MotionEvent.ACTION_MOVE:
                // 单点旋转
                if (mode == ROTATE) {
                    moveMatrix.set(downMatrix);
                    float deltaRotation = getSpaceRotation(event) - oldRotation;
                    moveMatrix.postRotate(deltaRotation, imageMidPoint.x, imageMidPoint.y);
                    matrix.set(moveMatrix);
                    invalidate();
                }
                // 单点缩放
                else if (mode == ZOOM_SINGLE) {
                    moveMatrix.set(downMatrix);
                    float scale = getSingleTouchDistance(event) / oldDistance;
                    moveMatrix.postScale(scale, scale, imageMidPoint.x, imageMidPoint.y);
                    matrix.set(moveMatrix);
                    invalidate();
                }
                // 多点缩放
                else if (mode == ZOOM_MULTI) {
                    moveMatrix.set(downMatrix);
                    float scale = getMultiTouchDistance(event) / oldDistance;
                    moveMatrix.postScale(scale, scale, midPoint.x, midPoint.y);
                    matrix.set(moveMatrix);
                    invalidate();
                }
                // 平移
                else if (mode == TRANS) {
                    moveMatrix.set(downMatrix);
                    moveMatrix.postTranslate(event.getX() - downX, event.getY() - downY);
                    matrix.set(moveMatrix);
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
                mode = NONE;
                break;
            default:
                break;
        }
        return true;
    }

获取中心点

    /**
     * 获取手势中心点
     *
     * @param event
     */
    private PointF getMidPoint(MotionEvent event) {
        PointF point = new PointF();
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
        return point;
    }

    /**
     * 获取图片中心点
     */
    private PointF getImageMidPoint() {
        PointF point = new PointF();
        float[] points = getBitmapPoints(srcImage, moveMatrix);
        float x1 = points[0];
        float x2 = points[2];
        float y2 = points[3];
        float y4 = points[7];
        point.set((x1 + x2) / 2, (y2 + y4) / 2);
        return point;
    }

获取单点缩放和多点缩放的点与点之间距离

    /**
     * 【多点缩放】获取手指间的距离
     *
     * @param event
     * @return
     */
    private float getMultiTouchDistance(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * 【单点缩放】获取手指和图片中心点的距离
     *
     * @param event
     * @return
     */
    private float getSingleTouchDistance(MotionEvent event) {
        float x = event.getX(0) - imageMidPoint.x;
        float y = event.getY(0) - imageMidPoint.y;
        return (float) Math.sqrt(x * x + y * y);
    }

针对触摸事件的边界判断

    /**
     * 检查边界
     *
     * @param x
     * @param y
     * @return true - 在边界内 | false - 超出边界
     */
    private boolean isTranslationActionCheck(Bitmap bitmap, float x, float y) {
        if (bitmap == null) return false;
        float[] points = getBitmapPoints(bitmap, moveMatrix);
        float x1 = points[0];
        float y1 = points[1];
        float x2 = points[2];
        float y2 = points[3];
        float x3 = points[4];
        float y3 = points[5];
        float x4 = points[6];
        float y4 = points[7];
        float edge = (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        if ((2 + Math.sqrt(2)) * edge >= Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2))
                + Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2))
                + Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2))
                + Math.sqrt(Math.pow(x - x4, 2) + Math.pow(y - y4, 2))) {
            return true;
        }
        return false;
    }

    /**
     * 判断手指触摸的区域是否在顶点的操作按钮内
     *
     * @param event
     * @param rect
     * @return
     */
    private boolean isInActionCheck(MotionEvent event, Rect rect) {
        int left = rect.left;
        int right = rect.right;
        int top = rect.top;
        int bottom = rect.bottom;
        return event.getX(0) >= left && event.getX(0) <= right && event.getY(0) >= top && event.getY(0) <= bottom;
    }

通过以上的逻辑,我们即可完成这种贴纸的简单效果。以下是源码:
StickerView

当然,本例依然存在一些手势触摸的问题。这些问题在大家的开发中肯定也会遇到,我会尽力修复这些问题。并且多多提供一些案例,与大家共同思考。

你可能感兴趣的:(一步步实现贴纸效果)