前言
好久没有写文章了,最近在学习手势绘制时开发了一个简易的电子白板demo,后续会单独写一个电子白板的项目开发实战,在开发当中处理白板中各种手势时,增加矩阵的效果,但是在使用矩阵时碰到了发生矩阵变换后,需要重新计算当前相对的点位从而不影响当前手势的正确绘制,所以在我踩坑后,特地写下这篇文章,希望对你有帮助
效果视频
逻辑
这里不做详细的绘制逻辑,单提供绘制以及矩阵相对点位思想和示例代码
画布
class CustomWhiteBoard extends CustomPainter {
WhiteBoardHandle whiteBoardHandle = WhiteBoardHandle();
CustomWhiteBoard(this.whiteBoardHandle) : super(repaint: whiteBoardHandle);
@override
void paint(Canvas canvas, Size size) {
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.saveLayer(rect, Paint());
onMatrix4(canvas, size);
//...绘制
canvas.restore();
// TODO: implement paint
}
@override
bool shouldRepaint(covariant CustomWhiteBoard oldDelegate) {
return true;
}
// ...这里是绘制矩阵
void onMatrix4(Canvas canvas,Size size){
final Matrix4 result = Matrix4.identity();
result.multiply(whiteBoardHandle.matrix4.value);
canvas.transform(result.storage);
}
}
画板这里就是一个很简单的绘制矩阵而已,不过多讲解具体画板绘制
功能实现
我当前使用手势为:ScaleGestureRecognizer (缩放手势识别器) 非使用 PanGestureRecognizer
ScaleGestureRecognizer 和PanGestureRecognizer 手势不能一起使用,但是ScaleGestureRecognizer 中包含的功能已经足够使用了,不需要PanGestureRecognizer
这里是手势工厂基础代码
Map<Type, GestureRecognizerFactory> getGesture(){ var scaleGesture = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(() => ScaleGestureRecognizer(), (instance) { instance.onStart = onScaleStart; instance.onUpdate = onScaleUpdate; instance.onEnd = onScaleEnd; }); return { ScaleGestureRecognizer: scaleGesture }; }
绑定在视图的RawGestureDetector的gesture参数中即可
我们基础的手势和绘制逻辑好了,接下来着重一下手势的相关实现代码Start -> End
定义矩阵
在你的State中定义两个基础矩阵 Matrix4 matrix 和 Matrix4 tempMatrix
matrix用于全局矩阵,tempMatrix用于临时矩阵,用于保留矩阵状态
手势实现逻辑
我们先来思考一下矩阵在每个手势状态需要负责的逻辑
- Start(开始状态) 这里主要负责当前手势中的触点,一般用于平移的起始点缓存
- Update(修改状态) 这里主要负责矩阵三个核心使用点(平移、缩放、旋转)的相关逻辑处理
- End(结束状态) 这里主要负责tempMatrix(缓存矩阵)的缓存
既然基础思路有了,那我们具体看看实现代码
Start
void onScaleStart(ScaleStartDetails details){
//校验手势触点
if(details.pointerCount == 1){
//缓存手势起始点
state.handle.translateStartOffset = details.focalPoint;
}
}
Update
void onScaleUpdate(ScaleUpdateDetails details){
//当前画笔状态 这里是个枚举用来区分矩阵处理
switch(state.paintType.value){
case PaintType.scale:
state.handle.changeMatrix4(state.handle.tempMatrix.multiplied(Matrix4.diagonal3Values(scale,scale,1)));
break;
case PaintType.rotate:
state.handle.changeMatrix4(state.handle.recordMatrix.multiplied(Matrix4.rotationZ(details.rotation)));
break;
case PaintType.translate:
state.handle.changeMatrix4(Matrix4.translationValues(
details.focalPoint.dx - state.handle.scaleOffset.dx,
details.focalPoint.dy - state.handle.scaleOffset.dy,
1
).multiplied(state.handle.recordMatrix4));
break;
case PaintType.paint || PaintType.setting || PaintType.clear:
//绘制逻辑
break;
}
}
这里主要提要两点
①、代码中的changeMatrix4(Matrix4 newMatrix4) 核心逻辑就是更新matrix矩阵的状态而已
②、multiplied(Matrix4 newMatrix4)方法 为当前矩阵matrix4乘法(复制)新矩阵到当前的矩阵中
diagonal3Values(矩阵缩放 x ,y,z) 和 rotationZ(旋转z) 和 translationValues(平移 x,y,z)
end
void onScaleEnd(ScaleEndDetails details){
//这里没什么好讲的,就是tempMatrix = matrix ,手势停止后缓存当前手势以保留矩阵状态
state.handle.saveRecordMatrix4();
}
补充
你或许也会有疑问,当画布应用了矩阵变化之后,整个画布会被矩阵影响,从而导致当前手势的点位信息会有偏差,而这种情况下怎么处理呢?
我们需要 获取矩阵偏移量后的相对点位 ,在获取相对点位之前,需要考虑到平移和旋转的逻辑计算,经过我的测试,先计算旋转后的偏移量再计算平移的偏移量会导致点位偏差,缩放而不影响。
所以正确点位计算思想为:先计算平移后的偏移量之后再获取旋转的偏移量,这样拿到的点位则是正确的
旋转值的获取和处理
可能你会问,ScaleUpdateDetails类的参数下不是有rotation获取旋转值吗?
但是它的旋转值偏移量是基于你手势中心点得到旋转值,这个值对于我们相对点位计算来说是有误的,我们应该获取矩阵自身的旋转值 这个值Matrix4中没有直接提供,但是提供了 Matrix3 Matrix4.getRotation()的方法,我们需要从这个方法中重新获取可用的矩阵角度值
获取Matrix3的旋转角度方法
// 获取矩阵的旋转角度(以度数为单位)
double getRotationAngle(vector.Matrix3 matrix) {
// 提取旋转部分
double sinTheta = matrix.entry(1, 0); // 获取旋转矩阵的 sin(θ) 值
double cosTheta = matrix.entry(0, 0); // 获取旋转矩阵的 cos(θ) 值
// 计算旋转角度(弧度)
double rotationAngle = math.atan2(sinTheta, cosTheta);
// 将弧度转换为度数
double rotationDegrees = rotationAngle * (180 / math.pi);
return rotationDegrees;
}
除此之外,我们还需要根据点位信息和角度,转化为相对点位
首先先将当前矩阵的角度转化为弧度
// 将角度转换为弧度
var radians = angle * (math.pi / 180.0);
我们利用旋转矩阵公式
rotatedX=offset.dx×cos(angle)−offset.dy×sin(angle)
rotatedY=offset.dx×sin(angle)+offset.dy×cos(angle)
获取到相对矩阵偏移量
Offset _rotateOffset(Offset offset, double angle) {
// 将角度转换为弧度
var radians = angle * (math.pi / 180.0);
var rotatedX = offset.dx * math.cos(radians) - offset.dy * math.sin(radians);
var rotatedY = offset.dx * math.sin(radians) + offset.dy * math.cos(radians);
return Offset(rotatedX, rotatedY);
}
实现获取相对点位
直接贴代码吧,一看就明白
getRealOffset(Offset offset){
var matrix = matrix4.value;
// 获取矩阵平移的x,y
var translationX = matrix.getTranslation().x;
var translationY = matrix.getTranslation().y;
// 获取当前矩阵的缩放值
double scale = matrix.getMaxScaleOnAxis();
var matrix3 = matrix.getRotation();
double rotate = getRotationAngle(matrix3);
// 这里的Offset为,当前手势点位的Offset - 平移后的Offset
var rotatedOffset = _rotateOffset(offset - Offset(translationX, translationY), -rotate);
// 将偏移量向量旋转回原始坐标系
print('平移参数: (${translationX},${translationY}),缩放参数: ${scale},旋转角度: ${rotate},旋转偏移量: ${rotatedOffset}');
return rotatedOffset / scale;
}
至此结束。
I was recommended this website by my cousin I am not sure whether this post is written by him as nobody else know such detailed about my difficulty You are wonderful Thanks
Your blog is a true gem in the world of online content. I’m continually impressed by the depth of your research and the clarity of your writing. Thank you for sharing your wisdom with us.
I loved as much as you will receive carried out right here The sketch is tasteful your authored subject matter stylish nonetheless you command get got an edginess over that you wish be delivering the following unwell unquestionably come further formerly again as exactly the same nearly very often inside case you shield this hike