一步步实现贴纸效果

作者: 1409d11db72f | 来源:发表于2016-08-26 18:09 被阅读1015次

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

    大家先看看图片效果:


    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

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

    相关文章

      网友评论

      • Judy_li:请问isTranslationActionCheck方法的参数x,y传入的是什么

      本文标题:一步步实现贴纸效果

      本文链接:https://www.haomeiwen.com/subject/jpjzsttx.html