Compose实现RactResizeView(类似图片切割选择器)

461 阅读6分钟

原谅我这么描述,我也不知道怎么描述!!!

1、需求描述

实现一个根据位置和大小实时绘制矩形,当点击调整按钮时,调整最后一次矩形的大小和位置。调整完成后继续绘制。根据调整的位置继续跟踪绘制。具体效果看视频。嘿嘿!

2、思路分析

2.1 两种状态

  • 实时跟踪状态,没有进行拖拽的点,不允许调整
  • 调整状态:有八个点可以对矩形进行调整。

2.2 需要绘制的东西

  • 矩形(四条边)
  • 矩形+八个顶点

2.3 操控思路

根据touch的点,和手指的移动偏移量,分别调整宽高和矩形的起始位置。 根据touch的起始点,判断是否是进行移动操作,还是调整操作。

3、具体实现

3.1 通过flag区分是跟踪状态还是,调整状态。画矩形和八个点

在ViewModel里创建需要的标志位。和Canvas的Size 和左上顶点位置,也是组件起始位置

class CameraViewModel : ViewModel() {
    var size = mutableStateOf(Size(200f,200f))
    var topLeft = mutableStateOf(Offset(0f,0f))
    var isMove = mutableStateOf(true)
    fun setSize(width: Float, height: Float){
        size.value = Size(width, height)
    }
    fun setTopLeft(x: Float, y: Float){
        topLeft.value = Offset(x, y)
    }
    //模拟实时跟踪
    fun move(){
        isMove.value = true
       scopeNetLife {
         while (isMove.value){
             setTopLeft(topLeft.value.x+5, topLeft.value.y+5)
                delay(15)
         }
       }
    }
    //停止移动
    fun stop(){
        isMove.value = false
    }
}

八个顶点 分别是 四个顶点, 和四条线段的中点。它们的坐标,根据左上的点,高度和宽度都能求出来。

image.png 为了方便理解,我们把八个顶点分别标记成ABCDEFGH。分别求出坐标。 我们画矩形,需要知道起点和宽高,画圆圈需要圆圈的中心点和半径,所以我们要求各个点的坐标 A是矩形的起点,是传进来的。其他点都是算出来的。

//Canvas的onDraw
{
    drawRect(color = Color.Red, topLeft = offset, size,
        style = Stroke(1f))
    if(!isMove){
        drawCircle(color = Color.Green, center = offset, radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x+size.width/2,offset.y),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x+size.width,offset.y),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x,offset.y+size.height),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x,offset.y+size.height/2),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x+size.width,offset.y+size.height),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x+size.width/2,offset.y+size.height),radius = 10f)
        drawCircle(color = Color.Green, center = Offset(offset.x+size.width,offset.y+size.height/2),radius = 10f)
    }

只要ViewModel 的 size 和 offset 发生变化,视图就会对应改变。我们介绍两个类,Offset 和Size。
Offset 里面封装了两个值,x和y。我们可以理解为点的坐标或者是偏移量。里面的类对操作符进行了重载,可以直接对对象进行,加减乘除和取模。有兴趣的同学可以去看看Offset里面的代码。我们这次只用到了加减操作。
前面写C++的文章刚写了操作符重载的知识(点击这里可以查看C++操作符的知识)(写之前在Kotlin没用过)。今天在Kotlin就用到了。 还可以获取到原点的距离等等。 Size 里面封装了两个值,width和height。这个类只重载了乘除操作。

3.2 进入调整模式后,拖动矩形里边的区域,移动矩形的位置。

通过Compose 的pointerInteropFilter回调,对touch事件进行拦截处理。我们每一次触摸事件,是一组事件。按下移动和抬起。我们在不同的事件,处理不同的数据,从而达到操控矩形的目的。 我们在ActionDown 时,判断坐标是否在矩形区域内 。在ActionMove里计算手指偏移量。移动矩形的位置。 ActionUp。此状态结束,将状态值置false

ACTION_DOWN->{
    Log.e("TAG", "ACTION_DOWN: ")
    //获取按下时的坐标
    var touchOffset = Offset(motionEvent.x,motionEvent.y)
    //判断按下位置 是否在 矩形里边
    if(touchOffset.x>offset.x+20 && touchOffset.x<offset.x+size.width-20 && touchOffset.y>offset.y+20 && touchOffset.y<offset.y+size.height-20){
        cameraViewModel.pointerMove= true//手指滑动 移动矩形标志位
        cameraViewModel.pointerOffset=touchOffset
    }
 }

image.png 上面的代码判断是否在粉色区域内。

ACTION_MOVE ->{
    Log.e("TAG", "ACTION_MOVE: ")
    if(cameraViewModel.pointerMove){
        var touchOffset = Offset(motionEvent.x,motionEvent.y)
        var newOffset =touchOffset.minus(cameraViewModel.pointerOffset)
        cameraViewModel.topLeft.value= cameraViewModel.topLeft.value.plus(newOffset)
        cameraViewModel.pointerOffset=touchOffset
        Log.e("TAG", "ACTION_MOVE: "+ newOffset)
    }

计算手指触摸偏移量,移动矩形的位置。
移动的位置 - 起始的位置 =偏移量
矩形位置 +偏移量 =新位置
这两句话应该好理解吧。例如手指的起始点 是 (1,1)移动的最终位置是(5,5),他的偏移量就是(4,4)起始点,先沿X轴移动四格,再沿Y轴移动四格,就到了(5,5),矩形的起始点,加上偏移量,就是矩形的移动偏移量。这样就能做到手指和矩形一块移动,移动相同的距离。(初中知识)

  • ActionDown 记录触摸点
  • 判断是否在粉色区域内。进入手指触摸移动矩形模式。
  • ActionMove获取最终移动的位置,计算手指偏移量
  • 改变矩形位置 矩形旧位置 +偏移量
  • 更新起始点位置
  • ActionUp 退出手指触摸移动矩形模式

3.3 调整模式,拖动八个顶点,调整矩形大小和位置。

思路和拖拽移动矩形是差不多的,先判断触摸起始点是否在 A点附近,判断的是A点为中心点边长为40的矩形内。


ACTION_DOWN->{
    if(touchOffset.x< offset.x +20 && touchOffset.x >offset.x-20 && touchOffset.y<offset.y +20 && touchOffset.y >offset.y-20){
        cameraViewModel.dragonPosition = DragonPosition.LEFT_TOP
        cameraViewModel.pointerResize = true
        cameraViewModel.pointerOffset=touchOffset

    }
}

image.png 判断触摸点 是否在橙色区域内。是就根据偏移量移动矩形位置,和改变矩形大小

DragonPosition.LEFT_TOP->{
    Log.e("TAG", "LEFT_TOP: ")
    var touchOffset = Offset(motionEvent.x,motionEvent.y)
    var newOffset =touchOffset.minus(cameraViewModel.pointerOffset)
    cameraViewModel.setSize(cameraViewModel.size.value.width-newOffset.x,cameraViewModel.size.value.height-newOffset.y)
    cameraViewModel.topLeft.value =cameraViewModel.topLeft.value.plus(newOffset)
    cameraViewModel.pointerOffset=touchOffset
}

A 点拖动,矩形的位置的偏移量是和手指的偏移量是相同的直接加即可,但是宽高整好和偏移量是相反的,往上滑动,矩形的高是变长的,往下滑 是变短的。左右同理。所以宽高 要减去偏移量。其他的点的拖动同理。
B 只计算手指Y轴的偏移量 ,只改变矩形位置的 Y坐标和矩形的高。矩形位置,和手指便宜量 是相同,矩形的高和偏移量相反。
C 计算手指偏移量,矩形位置只改变 Y坐标,和手指Y偏移量相同。宽度变化和手指X偏移量相同,高度和手指Y偏移相反,其他点同理

image.png

这样就完成了大小和位置的调整。

4、需要优化的点

  • 矩形绘制边界问题。矩形的绘制不要超过总控件的大小。移动和调整大小的时候也不要超过边界。
  • 调整时,下边往上的偏移量不要超过上边,上边往下偏移是不要超过下边。左边往右偏移不要超过右边,右边往左偏移不要超过左边。
  • onDraw里面不要创建对象。touch事件里,能复用的对象尽量能复用。避免内存碎片,导致oom
  • 如果作为一个控件。需要对矩形size 和 offset ,矩形线条粗细,颜色,顶点半径和颜色,进行对外封装。