Android画板(二):Matrix实现美图APP的旋转缩放

一、美图旋转缩放方案

最近遇到了对图像进行旋转缩放操作的需求,以往的双指缩放、双指旋转体验明显没有美图app的旋转缩放方式好:


美图APP的图像旋转缩放操作方案

所以按照这个方案来实现,实现效果如下:


白板Demo实现后效果

二、实现思路及代码

Android的图像旋转缩放,首先想到的是Matrix:Android Matrix原理
原理比较复杂,总结一句话:Matrix是记录图形变换数据的矩阵

实现思路:

1.获取bitmap的原始尺寸,及图形的关键定位点

【Bitmap手动压缩下一篇讲】

        //读取Bitmap的矩形区域**RectF(0,0,width,height)** 
        RectF photoRectSrc = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        //Bitmap的初始关键定位点
        //0,1代表左上角点XY,2,3代表右上角点XY,4,5代表右下角点XY,6,7代表左下角点XY,8,9代表中心点XY
        float[] photoCornersSrc = new float[10];
        //读取Bitmap关键定位点
        photoCornersSrc[0] = photoRectSrc.left;
        photoCornersSrc[1] = photoRectSrc.top;
        photoCornersSrc[2] = photoRectSrc.right;
        photoCornersSrc[3] = photoRectSrc.top;
        photoCornersSrc[4] = photoRectSrc.right;
        photoCornersSrc[5] = photoRectSrc.bottom;
        photoCornersSrc[6] = photoRectSrc.left;
        photoCornersSrc[7] = photoRectSrc.bottom;
        photoCornersSrc[8] = photoRectSrc.centerX();
        photoCornersSrc[9] = photoRectSrc.centerY();
Android画板(二):Matrix实现美图APP的旋转缩放_第1张图片
Bitmap的原始显示区域
2.判断手势动作来改变Matrix的参数

判断点击点是否图像当前显示区域:

private boolean isInPhotoRect(Matrix matrix, RectF photoRectSrc,float[] downPoint) {
            float[] invertPoint = new float[2];//逆变换后的点击点数组
            Matrix invertMatrix = new Matrix();//当前Matrix矩阵的逆矩阵
            matrix.invert(invertMatrix);//通过当前Matrix得到对应的逆矩阵数据
            invertMatrix.mapPoints(invertPoint, downPoint);//通过逆矩阵变化得到逆变换后的点击点
            return photoRectSrc.contains(invertPoint[0], invertPoint[1]);//判断逆变换后的点击点是否在图像初始RectF内
    }

计算点击边角操作图标时的缩放倍数及旋转角度

 private void onRotateAction(Matrix matrix,float[] photoCorners) {
        //放大
        //目前触摸点与图片显示中心距离
        float a = (float) Math.sqrt(Math.pow(curX - photoCorners[8], 2) + Math.pow(curY - photoCorners[9], 2));
        //目前上次旋转图标与图片显示中心距离
        float b = (float) Math.sqrt(Math.pow(photoCorners[4] - photoCorners[0], 2) + Math.pow(photoCorners[5] - photoCorners[1], 2)) / 2;

        //设置Matrix缩放参数
        double photoLen = Math.sqrt(Math.pow(record.photoRectSrc.width(), 2) + Math.pow(record.photoRectSrc.height(), 2));
        if (a >= photoLen / 2 * SCALE_MIN && a >= SCALE_MIN_LEN && a <= photoLen / 2 * SCALE_MAX) {
            //这种计算方法可以保持旋转图标坐标与触摸点同步缩放
            float scale = a / b;
            record.matrix.postScale(scale, scale, photoCorners[8], photoCorners[9]);
        }

        //旋转
        //根据移动坐标的变化构建两个向量,以便计算两个向量角度.
        PointF preVector = new PointF();
        PointF curVector = new PointF();
        preVector.set(preX - photoCorners[8], preY - photoCorners[9]);//旋转后向量
        curVector.set(curX - photoCorners[8], curY - photoCorners[9]);//旋转前向量
        //计算向量长度
        double preVectorLen = getVectorLength(preVector);
        double curVectorLen = getVectorLength(curVector);
        //计算两个向量的夹角.
        double cosAlpha = (preVector.x * curVector.x + preVector.y * curVector.y)
                / (preVectorLen * curVectorLen);
        //由于计算误差,可能会带来略大于1的cos,例如
        if (cosAlpha > 1.0f) {
            cosAlpha = 1.0f;
        }
        //本次的角度已经计算出来。
        double dAngle = Math.acos(cosAlpha) * 180.0 / Math.PI;
        // 判断顺时针和逆时针.
        //判断方法其实很简单,这里的v1v2其实相差角度很小的。
        //先转换成单位向量
        preVector.x /= preVectorLen;
        preVector.y /= preVectorLen;
        curVector.x /= curVectorLen;
        curVector.y /= curVectorLen;
        //作curVector的逆时针垂直向量。
        PointF verticalVec = new PointF(curVector.y, -curVector.x);

        //判断这个垂直向量和v1的点积,点积>0表示俩向量夹角锐角。=0表示垂直,<0表示钝角
        float vDot = preVector.x * verticalVec.x + preVector.y * verticalVec.y;
        if (vDot > 0) {
            //v2的逆时针垂直向量和v1是锐角关系,说明v1在v2的逆时针方向。
        } else {
            dAngle = -dAngle;
        }
        matrix.postRotate((float) dAngle, photoCorners[8], photoCorners[9]);
    }

Matrix操作:

matrix.postTranslate(x, y);//图像偏移的X、Y轴距离
matrix.postScale(scale, scale, centerX, centerY);//图像根据缩放中心点缩放的倍数
matrix.postRotate(angle, centerX, centerY);//图像根据旋转中心点旋转的弧度
3.根据Matrix变换得到新的图形关键定位点
float[] photoCorners = new float[10];//变换后的关键定位点数组
matrix.mapPoints(photoCorners, photoCornersSrc);//通过matrix进行变换,得到新的关键定位点数据
4.绘制Matrix变换后的图像、边框线及边角操作图标
     //绘制变换后图像
     private void drawBitmap(Canvas canvas, Bitmap bitmap, Matrix matrix) {
        canvas.drawBitmap(bitmap,matrix, null);//根据Matrix绘制变化后的Bitmap
     }
        //绘制图像边线(由于图像旋转后不一定是矩形,所以用Path绘制边线)
    private void drawBoard(Canvas canvas, float[] photoCorners) {
        Path photoBorderPath = new Path();
        photoBorderPath.moveTo(photoCorners[0], photoCorners[1]);
        photoBorderPath.lineTo(photoCorners[2], photoCorners[3]);
        photoBorderPath.lineTo(photoCorners[4], photoCorners[5]);
        photoBorderPath.lineTo(photoCorners[6], photoCorners[7]);
        photoBorderPath.lineTo(photoCorners[0], photoCorners[1]);
        canvas.drawPath(photoBorderPath, boardPaint);
    }

    //绘制边角操作图标
   private void drawMarks(Canvas canvas, float[] photoCorners) {
        float x;
        float y;
        x = photoCorners[0] - markerCopyRect.width() / 2;
        y = photoCorners[1] - markerCopyRect.height() / 2;
        markerCopyRect.offsetTo(x, y);
        canvas.drawBitmap(mirrorMarkBM, x, y, null);

        x = photoCorners[2] - markerDeleteRect.width() / 2;
        y = photoCorners[3] - markerDeleteRect.height() / 2;
        markerDeleteRect.offsetTo(x, y);
        canvas.drawBitmap(deleteMarkBM, x, y, null);

        x = photoCorners[4] - markerRotateRect.width() / 2;
        y = photoCorners[5] - markerRotateRect.height() / 2;
        markerRotateRect.offsetTo(x, y);
        canvas.drawBitmap(rotateMarkBM, x, y, null);

        x = photoCorners[6] - markerResetRect.width() / 2;
        y = photoCorners[7] - markerResetRect.height() / 2;
        markerResetRect.offsetTo(x, y);
        canvas.drawBitmap(resetMarkBM, x, y, null);
    }

三、Github源码

白板Demo源码,欢迎一起学习交流!

你可能感兴趣的:(Android画板(二):Matrix实现美图APP的旋转缩放)