原谅我这么描述,我也不知道怎么描述!!!
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
}
}
八个顶点 分别是 四个顶点, 和四条线段的中点。它们的坐标,根据左上的点,高度和宽度都能求出来。
为了方便理解,我们把八个顶点分别标记成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
}
}
上面的代码判断是否在粉色区域内。
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
}
}
判断触摸点 是否在橙色区域内。是就根据偏移量移动矩形位置,和改变矩形大小
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偏移相反,其他点同理
这样就完成了大小和位置的调整。
4、需要优化的点
- 矩形绘制边界问题。矩形的绘制不要超过总控件的大小。移动和调整大小的时候也不要超过边界。
- 调整时,下边往上的偏移量不要超过上边,上边往下偏移是不要超过下边。左边往右偏移不要超过右边,右边往左偏移不要超过左边。
- onDraw里面不要创建对象。touch事件里,能复用的对象尽量能复用。避免内存碎片,导致oom
- 如果作为一个控件。需要对矩形size 和 offset ,矩形线条粗细,颜色,顶点半径和颜色,进行对外封装。