如何在Flutter中创建类似Ins故事编辑的自定义字体样式与颜色UI?
Flutter实现自定义故事文本编辑器(支持字体样式与颜色选择)
参考效果示意图:
核心功能实现思路
- 可交互文本组件:通过手势监听实现文本的拖拽、缩放,用
Transform组件包裹文本完成变换 - 字体样式控制:提供字体家族、粗细、斜体选项,动态更新
TextStyle参数 - 颜色选择:集成颜色选择器,支持自定义颜色拾取
- 文本编辑:点击文本唤起输入框,实时更新内容
分步实现代码示例
1. 依赖准备(可选)
如果使用第三方颜色选择器,在pubspec.yaml中添加:
dependencies: flutter_colorpicker: ^1.0.3
2. 完整编辑器代码
import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; class StoryEditor extends StatefulWidget { const StoryEditor({super.key}); @override State<StoryEditor> createState() => _StoryEditorState(); } class _StoryEditorState extends State<StoryEditor> { // 文本核心状态 String _textContent = '输入你的文字'; Color _textColor = Colors.white; double _fontSize = 40; FontWeight _fontWeight = FontWeight.normal; FontStyle _fontStyle = FontStyle.normal; String _selectedFont = 'Roboto'; // 文本变换状态 Offset _position = const Offset(100, 200); double _scale = 1.0; // 可选字体列表(可根据设备支持的字体调整) final List<String> _availableFonts = [ 'Roboto', 'Arial', 'Georgia', 'Times New Roman', 'Courier New' ]; // 唤起颜色选择器 void _showColorPicker() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('选择文本颜色'), content: SingleChildScrollView( child: ColorPicker( pickerColor: _textColor, onColorChanged: (color) => setState(() => _textColor = color), showLabel: true, pickerAreaHeightPercent: 0.8, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定'), ), ], ), ); } // 唤起文本编辑框 void _editText() { final controller = TextEditingController(text: _textContent); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('编辑文字'), content: TextField(controller: controller, autofocus: true), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () { setState(() => _textContent = controller.text); Navigator.pop(context); }, child: const Text('确定'), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[900], body: Stack( children: [ // 背景图(可替换为用户选择的图片) Container( decoration: const BoxDecoration( image: DecorationImage( image: NetworkImage('https://picsum.photos/800/1200'), fit: BoxFit.cover, ), ), ), // 可交互文本 Positioned( left: _position.dx, top: _position.dy, child: GestureDetector( onTap: _editText, // 拖拽手势 onPanUpdate: (details) { setState(() { _position += details.delta; }); }, // 缩放手势 onScaleUpdate: (details) { setState(() { _scale = details.scale.clamp(0.5, 2.0); }); }, child: Transform.scale( scale: _scale, child: Text( _textContent, style: TextStyle( color: _textColor, fontSize: _fontSize, fontWeight: _fontWeight, fontStyle: _fontStyle, fontFamily: _selectedFont, ), ), ), ), ), // 底部样式控制面板 Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Colors.black54, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), child: Column( children: [ // 字体选择下拉框 DropdownButton<String>( value: _selectedFont, dropdownColor: Colors.black87, style: const TextStyle(color: Colors.white), items: _availableFonts.map((font) { return DropdownMenuItem( value: font, child: Text(font), ); }).toList(), onChanged: (value) { if (value != null) setState(() => _selectedFont = value); }, ), const SizedBox(height: 12), // 样式控制按钮组 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon(Icons.format_bold, color: _fontWeight == FontWeight.bold ? Colors.blue : Colors.white), onPressed: () => setState(() { _fontWeight = _fontWeight == FontWeight.bold ? FontWeight.normal : FontWeight.bold; }), ), IconButton( icon: Icon(Icons.format_italic, color: _fontStyle == FontStyle.italic ? Colors.blue : Colors.white), onPressed: () => setState(() { _fontStyle = _fontStyle == FontStyle.italic ? FontStyle.normal : FontStyle.italic; }), ), IconButton( icon: const Icon(Icons.color_lens, color: Colors.white), onPressed: _showColorPicker, ), IconButton( icon: const Icon(Icons.add, color: Colors.white), onPressed: () => setState(() => _fontSize += 2), ), IconButton( icon: const Icon(Icons.remove, color: Colors.white), onPressed: () => setState(() => _fontSize = _fontSize > 12 ? _fontSize - 2 : 12), ), ], ), ], ), ), ), ], ), ); } } // 运行入口 void main() { runApp(const MaterialApp(home: StoryEditor())); }
扩展优化建议
- 添加文本旋转功能:通过
GestureDetector监听旋转手势,更新Transform的旋转参数 - 支持多文本添加:维护文本组件列表,实现多段独立文本的编辑
- 导出编辑结果:使用
RepaintBoundary将编辑器内容转为图片保存 - 适配屏幕:通过
MediaQuery获取屏幕尺寸,动态调整文本初始位置和缩放范围
内容的提问来源于stack exchange,提问作者Miyad
相关产品推荐
相关产品推荐

