You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中创建类似Ins故事编辑的自定义字体样式与颜色UI?

Flutter实现自定义故事文本编辑器(支持字体样式与颜色选择)

参考效果示意图:
Instagram故事编辑器参考图

核心功能实现思路

  • 可交互文本组件:通过手势监听实现文本的拖拽、缩放,用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 19:27:31