旋转控件(一):矩形的平移旋转缩放

1,845 阅读5分钟

之前做了这个东西,一个内部素材加外操作边框,包含基本的移动、缩放、旋转,拉伸,快速定位,十字对齐等操作。常见使用场景如添加马赛克,添加画中画等。感觉比较有意思而且中间也遇到了一些问题就记录一下

先上图:

scaleRotae.gif

如图,这次就先讲一下平移、旋转、缩放

如果只是view做平移,有很多种实现方式比如通过layout、动画等。 基于我们的使用场景:知道一个图片的位置信息和旋转角度(中心旋转),就可以将它画出来。所以选择了维护一个rect以及绕中心旋转的角度rotation,这样一些第三方如需要c层操作的地方,直接把这些信息给c层也可以马上明确。

一、如何画

可以看出这个控件其实是一个图片+外面一个框,不过要考虑到旋转的情况,这里我们就借助matrix来做中心旋转。其实相对的就是先把canvas绕图片的中心做一次旋转

  @Override protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.concat(mRotateMatrix);
    mainDrawable.setBounds((int) mRect.left, (int) mRect.top, (int) mRect.right,
        (int) mRect.bottom);
    mainDrawable.draw(canvas);
    canvas.drawRect(mRect, mPaint);
    drawAnchors(canvas, mRect);
    canvas.restore();
    super.dispatchDraw(canvas);
  }

虚线就用paint设置setPathEffect就可以了,虚线框角上的几个角标也是借助rect的位置信息来画出来 比如画右上角的旋转按钮:

if (rotateDrawable != null) {
      //右上角
      rotateDrawable.setBounds(right - drawableWidth, top - drawableHeight, right + drawableWidth,
          top + drawableHeight);
      rotateDrawable.draw(canvas);
    }

重点就是去计算上面代码的mRotateMatrix

  private void invalidateMatrix() {
    mRotateMatrix.reset();
    mRotateMatrix.postTranslate(-mRect.centerX(), -mRect.centerY());
    mRotateMatrix.postRotate(mRotation);
    mRotateMatrix.postTranslate(mRect.centerX(), mRect.centerY());
  }

这里简单提一下matrix。学过线性代数的都知道矩阵吧,matrix其实就是个3x3的矩阵,里面的元素控制着旋转、缩放、平移、错切。直接new出来的矩阵是一个单位矩阵,描述的就是原来的图形信息,没有做变换。 然后就是矩阵计算不满足交换律,换句话说矩阵的前乘和后乘结果不一样,反应在matrix里面就是pre和post接口效果不一样。 简单点理解就是pre是放在操作队列头,post放在队尾,还有个set会清空整个队列再把它放进去。如果觉得担心记混,推荐就用post接口,符合先进先出的原则,先post的先执行。

二、如何判断什么时候该缩放、旋转或者平移

这里肯定是事件处理相关的了。处于方便,我们在ontouchEvent的时候把操作委托给GestureDetector,在onDown回调时判断点到了哪里,比如旋转、缩放、平移。然后在onScroll回调的时候通过每次的增量dx,dy计算缩放的比例、旋转角度以及移动距离。 在手指头按下时,由于我们知道图片rec的位置信息和角标位置信息,所以通过x和y可以判断是否点到了角标。 但是有个问题是,画的矩形是在旋转过的画布上面,我们手指头的xy是屏幕上的位置,这里对应不起来,会出现旋转后就点不到角标了 为了解决这个问题,手指头按下的point需要利用matix做一次映射。在这里的场景其实就相当于把旋转的信息考虑进去,其实也完全可以自己用三角函数算,但是matrix已经提供这种接口了,而且是调用的c层计算,效率应该更高些。

 final Matrix rotateMatrix = new Matrix();
    //反向旋转回去 抵消canvas的旋转
    rotateMatrix.postTranslate(-mRect.centerX(), -mRect.centerY());
    rotateMatrix.postRotate(-mRotation);
    rotateMatrix.postTranslate(mRect.centerX(), mRect.centerY());
    rotateMatrix.mapPoints(point);
    eventX = point[0];
    eventY = point[1];

    RectF rectF = mRect;
    //hit rotate  右上
    if (Math.abs(rectF.right - eventX) < drawableWidth * 2
        && Math.abs(rectF.top - eventY) < drawableHeight * 2) {
      return HitModes.ROTATE;
    }

需要注意的是这里matix的旋转角度和canvas的是相反的,其实就相当于转了n角度,然后又转回去n角度,相当于没有转。然后就可以按照没有旋转的情况判断有没有点到角标。

三、如何进行旋转、缩放、平移

第二步已经判断到用户想要进行什么操作了,接下来就是执行对应的操作了。 平移

先说简单的平移吧,上面说了,我们的场景是一个矩形位置信息+绕中心旋转角度。所以平移其实就是改矩形的位置罢了,直接调rec的offset,然后一定要记得在重新绘制前更新canvas的旋转矩阵

  private void onMove(float dx, float dy) {
    mRect.offset(-dx, -dy);
    invalidateMatrix();
    invalidate();
  }

缩放

首先我们要明确一个东西,就是缩放时旋转中心一定是不变的。所以可以算出中心和右下角的距离以及scroll后的中心和右下角的距离算出两个距离的变化当成x的变化。我们这里是等比例缩放,根据比例算出y的变化。 这里做了一个缩放最小的限制,缩放到1.5个角度宽高后就不嫩再缩小了,这样可以保证角标不会挤在一起。

  private void onScale(float dx, float dy) {
    // TODO: 2019/4/8 这里的dx,dy计算需要改进
    float[] pt1 = new float[] { mRect.centerX(), mRect.centerY() };
    float[] pt2 = new float[] { mRect.right, mRect.bottom };
    float[] pt3 = new float[] { mRect.right + dx, mRect.bottom + dy };
    float distance1 = getPointDistance(pt1, pt2);
    float distance2 = getPointDistance(pt1, pt3);
    float distance = distance1 - distance2;
    if (!checkCanScale(distance)) {
      return;
    }
    mRect.inset(-distance, -distance / mRatio);
    invalidateMatrix();
    invalidate();
  }

旋转

至于旋转其实也简单,因为旋转时中心也是不变的,类似缩放的操作。根据右上角的旋转角标和中心的角度,以及scroll后右上角和中心的角度,这两个的角度差就是旋转角度。 已知两个点的位置,通过math的atan2函数可以算出角度

  private void onRotate(float triggerX, float triggerY) {
    // TODO: 2019/4/8 这里的dx,dy计算需要改进
    float[] pt1 = new float[] { mRect.centerX(), mRect.centerY() };
    float[] pt2 = new float[] { mRect.right, mRect.top };
    float[] pt3 = new float[] { triggerX, triggerY };
    double angel1 = PointUtil.calculateAngleBetweenPoints(pt2, pt1);
    double angel2 = PointUtil.calculateAngleBetweenPoints(pt3, pt1);
    mRotation = (float) (angel1 - angel2);
    invalidateMatrix();
    invalidate();
  }

到这里基本的平移旋转缩放都介绍完了,详细的一些位置计算可以参考demo github.com/dynamicBai/… 给个star鼓励下呗

后面会逐步介绍:拉伸四边的操作(旋转中心会变化)、图片在屏幕上的快速定位和微调、移动时十字辅助线对齐等效果。