Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(九)

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字符串传递给后端保存,后续后端返回这个字符串再还原就可以进行编辑。这样就简单实现了基础的功能,后续就实现辅助的功能。

感兴趣的也可以关注我的微信公众号【前端学习小营地】,不定时会分享一些小功能~

好了,今天的分享到此结束,感谢阅读~拜拜~

相关推荐
千寻girling2 小时前
面试官: “ 说一下你对 Cookie 的理解 ? ”
前端·后端
RedHeartWWW2 小时前
nextjs中,关于Layout组件和Page组件的认知
前端·react.js
大明二代2 小时前
基于 Microsoft Graph API 与 React Email 构建现代化邮件发送系统
前端·react.js·microsoft
sujiu2 小时前
eslint匹配规则速通
前端
Zyx20072 小时前
用 Vue 3 构建任务清单:响应式编程的优雅实践
前端
风止何安啊2 小时前
那些让你 debug 到凌晨的陷阱,我帮你踩平了:React Hooks 避坑指南
前端·react.js·面试
用户279656042702 小时前
wx微信小程序部分逻辑
前端
大大花猫2 小时前
我用AI写了个小程序,却被人说没有底线…
前端·微信小程序·交互设计
梵尔纳多2 小时前
打包 Electron 程序
前端·javascript·electron