Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(九)
Flutter: 3.35.7
今天的功能完成那基本功能就完成了,所以给出github链接:
text
https://github.com/yhtqw/FrontEndDemo/tree/main/flutter_demo/lib/pages/transform_use/widgets/multiple_transform
前面我们简单实现了图片元素和文本元素的新增,优先实现的功能,很多地方还可以优化。今天我来讨论一下文本元素的拖动缩放。
对于文本元素,文本元素的高度是由文本自身的样式来决定的,所以我们限制文本元素只能缩放宽度,高度自动计算。这样就会对缩放区域进行元素类型判断,如果是文本元素,那么只缩放宽度,高度自适应;如果是其他元素,则同上对宽度高度进行缩放。既然功能又区别,那么icon也要有区别,那么之前的元素操作区域得做出更改,新增这种特殊功能区分元素类型对应图标不一样的配置:
dart
class ResponseAreaModel {
// 其他省略...
/// 元素类型不同展示不同的操作icon
final Map<String, String>? iconConfig;
// 其他省略...
}
class ConstantsConfig {
// 其他省略...
static final List<ResponseAreaModel> baseAreaList = [
// 其他省略...
// 缩放
ResponseAreaModel(
areaWidth: 20,
areaHeight: 20,
xRatio: 1,
yRatio: 1,
status: ElementStatus.scale.value,
icon: 'assets/images/icon_scale.png',
trigger: TriggerMethod.move,
iconConfig: {
ElementType.textType.type: 'assets/images/icon_scale_text.png',
},
),
// 其他省略...
],
// 其他省略...
}
// 其他省略...
// 如果选中,则展示操作区域
if (selected) ...areaList.map((item) => Positioned(
top: elementItem.elementHeight * item.yRatio - item.areaHeight / 2,
left: elementItem.elementWidth * item.xRatio - item.areaWidth / 2,
child: Container(
width: item.areaWidth,
height: item.areaHeight,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(item.areaWidth / 2),
),
child: Image.asset(
// 判断对应类型的图片是否单独存在
item.iconConfig?[elementItem.type] ?? item.icon,
width: item.areaWidth - ConstantsConfig.areaIconMargin,
height: item.areaHeight - ConstantsConfig.areaIconMargin,
fit: BoxFit.scaleDown,
color: Colors.white,
),
),
)),
运行效果:

这样我们就通过元素状态区分了特殊功能的图标展示,现在就是功能的实现了:
dart
/// 处理元素缩放
///
/// 通过移动点坐标[x]和[y]与按下的初始坐标,
void _onScale({required double x, required double y}) {
if (_currentElement?.type == ElementType.textType.type) {
_onScaleText(x: x, y: y);
} else {
_onScaleBase(x: x, y: y);
}
}
/// 抽取获取缩放需要的基础参数
(double, double, double, double, double) _getScaleParams({required double x, required double y}) {
final double oWidth = _temporary!.width;
final double oHeight = _temporary!.height;
final double oX = _temporary!.x;
final double oY = _temporary!.y;
final double resizeRatio = _calcResizeRatio(x: x, y: y);
return (oWidth, oHeight, oX, oY, resizeRatio);
}
/// 处理非文本元素的缩放
void _onScaleBase({required double x, required double y}) {
if (_currentElement == null || _temporary == null) return;
final (oWidth, oHeight, oX, oY, resizeRatio) = _getScaleParams(x: x, y: y);
double newW = oWidth * resizeRatio;
double newH = oHeight * resizeRatio;
final double minSize = ConstantsConfig.minSize;
// 以短边为基准来计算最小宽高
if (oWidth <= oHeight && newW < minSize) {
newW = minSize;
newH = minSize * oHeight / oWidth;
} else if (oHeight < oWidth && newH < minSize) {
newH = minSize;
newW = minSize * oWidth / oHeight;
}
// 以长边为基准来计算最大宽高
if (oWidth >= oHeight && newW >= _transformWidth) {
newW = _transformWidth;
newH = _transformWidth * oHeight / oWidth;
} else if (oHeight > oWidth && newH >= _transformHeight) {
newH = _transformHeight;
newW = _transformHeight * oWidth / oHeight;
}
if (
newW == _currentElement?.elementWidth &&
newH == _currentElement?.elementHeight
) {
return;
}
_currentElement = _currentElement?.copyWith(
elementWidth: newW,
elementHeight: newH,
x: oX - (newW - oWidth) / 2,
y: oY - (newH - oHeight) / 2,
);
_onChange();
}
/// 文本元素的缩放
void _onScaleText({required double x, required double y}) {
if (_currentElement == null || _temporary == null) return;
final (oWidth, oHeight, oX, oY, resizeRatio) = _getScaleParams(x: x, y: y);
double newW = oWidth * resizeRatio;
final double minSize = ConstantsConfig.minSize;
// 以短边为基准来计算最小宽高
if (oWidth <= oHeight && newW < minSize) {
newW = minSize;
}
// 以长边为基准来计算最大宽高
if (oWidth >= oHeight && newW >= _transformWidth) {
newW = _transformWidth;
}
final TextStyle style = _getTextStyle(_currentElement!.textOptions!);
final (tempWidth, tempHeight) = TransformUtils.calculateTextSize(
text: _currentElement!.textOptions!.text,
style: style,
maxWidth: newW,
);
_currentElement = _currentElement?.copyWith(
elementWidth: newW,
elementHeight: tempHeight,
x: oX - (newW - oWidth) / 2,
y: oY - (tempHeight - oHeight) / 2,
);
_onChange();
}
运行效果:

这样我们就实现了文本元素的拉伸效果。可以看到,随着功能的增加,可能就需要对之前的逻辑做更改,不过因为抽取了配置,很多地方就只需要加入少量的代码即可实现部分功能。
基础的功能差不多就完成了,接下来主功能就还有一个,那就是保存,我们不知道这个功能是否还存在编辑,如果不存在,直接将该结构转成图片返回即可,所以我们保存就直接将数据和图片都传递过去即可。一般这种大量数据存后端都是JSON字符串,所以我们直接转成json字符串存储:
dart
Future<void> _onSave() async {
if (_isLoading) return;
_isLoading = true;
if (_currentElement != null) {
setState(() {
_currentElement = null;
});
}
if (_currentElement == null) {
final String imagePath = await _getImagePath();
final List<Map<String, dynamic>> tempStringList = _elementList.map((item) => ElementModel.toJson(item)).toList();
widget.onSave(imgSrc: imagePath, data: jsonEncode(tempStringList));
_isLoading = false;
} else {
_isLoading = false;
_onSave();
}
}
运行效果:

这样我们就简单实现了图片的生成,并且将数据转换成JSON字符串传递给后端保存,后续后端返回这个字符串再还原就可以进行编辑。这样就简单实现了基础的功能,后续就实现辅助的功能。
感兴趣的也可以关注我的微信公众号【前端学习小营地】,不定时会分享一些小功能~
好了,今天的分享到此结束,感谢阅读~拜拜~