Flutter 框架跨平台鸿蒙开发 - 打造表情包制作器应用
);if (image!});A: 在pubspec.yaml中添加字体文件,然后在TextStyle中使用fontFamily属性。文字编辑:添加、编辑、样式调整贴纸系统:图标贴纸的添加和管理滤镜效果:ColorFilter矩阵实现多种滤镜拖拽操作:GestureDetector实现元素移动图片导出:RepaintBoundary捕获Widget为图片分享功能:share_plus实现图片分享旋
Flutter实战:打造表情包制作器应用
前言
表情包制作器是一款实用的图片编辑工具,让用户可以轻松制作个性化的表情包。本文将带你从零开始,使用Flutter开发一个功能完整的表情包制作器,支持添加文字、贴纸、滤镜等功能。
应用特色
- ✏️ 文字编辑:添加、编辑、移动文字
- 🎨 文字样式:字号、颜色、粗体、旋转
- 😊 贴纸系统:20种表情和图标贴纸
- 🎭 滤镜效果:黑白、怀旧、反色、模糊等7种滤镜
- 🎨 背景颜色:18种预设背景颜色
- 📱 拖拽操作:直观的拖拽移动元素
- 💾 导出分享:导出PNG图片并分享
- 🖼️ 实时预览:所见即所得的编辑体验
- 🎯 选中高亮:清晰的选中状态提示
- 🔄 旋转功能:文字和贴纸支持旋转
效果展示


数据模型设计
1. 文字元素
class TextElement {
String text;
Offset position;
double fontSize;
Color color;
FontWeight fontWeight;
String fontFamily;
double rotation;
TextAlign textAlign;
TextElement({
required this.text,
required this.position,
this.fontSize = 32,
this.color = Colors.white,
this.fontWeight = FontWeight.bold,
this.fontFamily = 'default',
this.rotation = 0,
this.textAlign = TextAlign.center,
});
}
2. 贴纸元素
class StickerElement {
IconData icon;
Offset position;
double size;
Color color;
double rotation;
StickerElement({
required this.icon,
required this.position,
this.size = 60,
this.color = Colors.white,
this.rotation = 0,
});
}
3. 滤镜类型
enum FilterType {
none('无滤镜'),
grayscale('黑白'),
sepia('怀旧'),
invert('反色'),
blur('模糊'),
brightness('增亮'),
contrast('对比度');
final String label;
const FilterType(this.label);
}
核心功能实现
1. 添加文字
void _addText() {
setState(() {
_textElements.add(TextElement(
text: '双击编辑',
position: const Offset(150, 200),
));
_selectedTextIndex = _textElements.length - 1;
_selectedStickerIndex = null;
});
}
2. 编辑文字
void _editText(int index) {
final controller = TextEditingController(text: _textElements[index].text);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('编辑文字'),
content: TextField(
controller: controller,
decoration: const InputDecoration(
hintText: '输入文字',
border: OutlineInputBorder(),
),
maxLines: 3,
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
setState(() {
_textElements[index].text = controller.text;
});
Navigator.pop(context);
},
child: const Text('确定'),
),
],
),
);
}
3. 拖拽移动元素
GestureDetector(
onPanUpdate: (details) {
setState(() {
element.position += details.delta;
});
},
onTap: () {
setState(() {
_selectedTextIndex = index;
_selectedStickerIndex = null;
});
},
onDoubleTap: () => _editText(index),
child: // 文字或贴纸
)
手势说明:
onPanUpdate:拖拽移动onTap:选中元素onDoubleTap:编辑文字
4. 滤镜实现
使用ColorFilter矩阵实现各种滤镜效果:
ColorFilter _getColorFilter() {
switch (_currentFilter) {
case FilterType.grayscale:
// 黑白滤镜
return const ColorFilter.matrix([
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]);
case FilterType.sepia:
// 怀旧滤镜
return const ColorFilter.matrix([
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0,
]);
case FilterType.invert:
// 反色滤镜
return const ColorFilter.matrix([
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
]);
case FilterType.brightness:
// 增亮滤镜
return const ColorFilter.matrix([
1.2, 0, 0, 0, 0,
0, 1.2, 0, 0, 0,
0, 0, 1.2, 0, 0,
0, 0, 0, 1, 0,
]);
case FilterType.contrast:
// 对比度滤镜
return const ColorFilter.matrix([
1.5, 0, 0, 0, -0.25 * 255,
0, 1.5, 0, 0, -0.25 * 255,
0, 0, 1.5, 0, -0.25 * 255,
0, 0, 0, 1, 0,
]);
default:
return const ColorFilter.mode(Colors.transparent, BlendMode.dst);
}
}
ColorFilter矩阵说明:
5×4矩阵,每行代表一个颜色通道的变换:
[R', G', B', A', offset]
R' = R×m[0] + G×m[1] + B×m[2] + A×m[3] + m[4]
G' = R×m[5] + G×m[6] + B×m[7] + A×m[8] + m[9]
B' = R×m[10] + G×m[11] + B×m[12] + A×m[13] + m[14]
A' = R×m[15] + G×m[16] + B×m[17] + A×m[18] + m[19]
黑白滤镜原理:
灰度值 = 0.2126×R + 0.7152×G + 0.0722×B
将RGB三个通道都设置为相同的灰度值
5. 导出图片
使用RepaintBoundary捕获Widget为图片:
Future<void> _exportImage() async {
try {
final boundary = _canvasKey.currentContext!.findRenderObject()
as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: 3.0);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
// 分享图片
await Share.shareXFiles(
[XFile.fromData(pngBytes, mimeType: 'image/png', name: 'meme.png')],
text: '我的表情包',
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('导出失败: $e')),
);
}
}
}
导出步骤:
- 获取RepaintBoundary的RenderObject
- 调用toImage()转换为ui.Image
- 转换为PNG字节数据
- 使用share_plus分享
UI组件设计
1. 画布渲染
Widget _buildCanvas() {
return Container(
width: 400,
height: 400,
decoration: BoxDecoration(
color: _backgroundColor,
border: Border.all(color: Colors.grey),
),
child: ColorFiltered(
colorFilter: _getColorFilter(),
child: Stack(
children: [
// 文字元素
..._textElements.asMap().entries.map((entry) {
// 渲染文字
}),
// 贴纸元素
..._stickerElements.asMap().entries.map((entry) {
// 渲染贴纸
}),
],
),
),
);
}
2. 文字渲染
Transform.rotate(
angle: element.rotation,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: _selectedTextIndex == index
? Border.all(color: Colors.blue, width: 2)
: null,
color: _selectedTextIndex == index
? Colors.blue.withOpacity(0.1)
: null,
),
child: Text(
element.text,
style: TextStyle(
fontSize: element.fontSize,
color: element.color,
fontWeight: element.fontWeight,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 2,
offset: const Offset(1, 1),
),
],
),
textAlign: element.textAlign,
),
),
)
文字效果:
- Transform.rotate:旋转
- Shadow:阴影描边
- 选中状态:蓝色边框和背景
3. 贴纸选择器
void _showStickerPicker() {
showModalBottomSheet(
context: context,
builder: (context) => Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'选择贴纸',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: _stickers.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () => _addSticker(_stickers[index]),
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
_stickers[index],
size: 40,
color: Colors.black87,
),
),
);
},
),
),
],
),
),
);
}
4. 文字样式编辑器
void _showTextStyleEditor() {
if (_selectedTextIndex == null) return;
final element = _textElements[_selectedTextIndex!];
showModalBottomSheet(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 字号滑块
Row(
children: [
const Text('字号:'),
Expanded(
child: Slider(
value: element.fontSize,
min: 16,
max: 72,
divisions: 28,
label: element.fontSize.round().toString(),
onChanged: (value) {
setModalState(() {
element.fontSize = value;
});
setState(() {});
},
),
),
],
),
// 颜色选择
// 粗体和旋转按钮
],
),
);
},
),
);
}
5. 工具栏
Widget _buildToolbar() {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildToolButton(
icon: Icons.text_fields,
label: '文字',
onPressed: _addText,
),
_buildToolButton(
icon: Icons.emoji_emotions,
label: '贴纸',
onPressed: _showStickerPicker,
),
_buildToolButton(
icon: Icons.filter,
label: '滤镜',
onPressed: _showFilterPicker,
),
_buildToolButton(
icon: Icons.palette,
label: '背景',
onPressed: _showBackgroundPicker,
),
],
),
],
),
);
}
技术要点详解
1. RepaintBoundary
用于捕获Widget为图片:
RepaintBoundary(
key: _canvasKey,
child: _buildCanvas(),
)
作用:
- 创建独立的渲染层
- 可以转换为图片
- 优化重绘性能
2. ColorFiltered
应用颜色滤镜:
ColorFiltered(
colorFilter: _getColorFilter(),
child: // 内容
)
3. Transform.rotate
旋转Widget:
Transform.rotate(
angle: element.rotation, // 弧度
child: // 内容
)
角度转换:
- 弧度 = 角度 × π / 180
- 90° = π/2
- 180° = π
- 360° = 2π
4. GestureDetector手势
GestureDetector(
onPanUpdate: (details) {
// 拖拽移动
element.position += details.delta;
},
onTap: () {
// 单击选中
},
onDoubleTap: () {
// 双击编辑
},
child: // 内容
)
5. StatefulBuilder
在BottomSheet中使用局部状态:
showModalBottomSheet(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) {
return // 内容
},
),
)
滤镜效果详解
1. 黑白滤镜
灰度值 = 0.2126×R + 0.7152×G + 0.0722×B
这个公式基于人眼对不同颜色的敏感度。
2. 怀旧滤镜
R' = 0.393×R + 0.769×G + 0.189×B
G' = 0.349×R + 0.686×G + 0.168×B
B' = 0.272×R + 0.534×G + 0.131×B
产生偏黄褐色的复古效果。
3. 反色滤镜
R' = 255 - R
G' = 255 - G
B' = 255 - B
颜色取反,产生负片效果。
4. 增亮滤镜
R' = R × 1.2
G' = G × 1.2
B' = B × 1.2
所有颜色通道乘以1.2,整体变亮。
5. 对比度滤镜
R' = (R - 128) × 1.5 + 128
G' = (G - 128) × 1.5 + 128
B' = (B - 128) × 1.5 + 128
增强明暗对比。
功能扩展建议
1. 图片导入
import 'package:image_picker/image_picker.dart';
Future<void> _pickImage() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_backgroundImage = File(image.path);
});
}
}
2. 更多字体
class TextElement {
String fontFamily;
static const List<String> fonts = [
'Roboto',
'Arial',
'Times New Roman',
'Courier New',
'Comic Sans MS',
];
}
3. 图层管理
class Layer {
String id;
LayerType type;
bool visible;
double opacity;
int zIndex;
Layer({
required this.id,
required this.type,
this.visible = true,
this.opacity = 1.0,
this.zIndex = 0,
});
}
enum LayerType {
text,
sticker,
image,
}
4. 撤销/重做
class HistoryManager {
List<EditorState> _history = [];
int _currentIndex = -1;
void push(EditorState state) {
_history = _history.sublist(0, _currentIndex + 1);
_history.add(state);
_currentIndex++;
}
EditorState? undo() {
if (_currentIndex > 0) {
_currentIndex--;
return _history[_currentIndex];
}
return null;
}
EditorState? redo() {
if (_currentIndex < _history.length - 1) {
_currentIndex++;
return _history[_currentIndex];
}
return null;
}
}
5. 模板系统
class MemeTemplate {
String id;
String name;
String thumbnail;
List<TextElement> textElements;
List<StickerElement> stickerElements;
Color backgroundColor;
MemeTemplate({
required this.id,
required this.name,
required this.thumbnail,
required this.textElements,
required this.stickerElements,
required this.backgroundColor,
});
}
List<MemeTemplate> templates = [
MemeTemplate(
id: 'template1',
name: '经典上下文字',
thumbnail: 'assets/templates/template1.png',
textElements: [
TextElement(text: '上方文字', position: Offset(200, 50)),
TextElement(text: '下方文字', position: Offset(200, 350)),
],
stickerElements: [],
backgroundColor: Colors.white,
),
];
6. 自定义贴纸
import 'dart:io';
class CustomSticker {
File imageFile;
Offset position;
double size;
double rotation;
CustomSticker({
required this.imageFile,
required this.position,
this.size = 100,
this.rotation = 0,
});
}
Future<void> _addCustomSticker() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_customStickers.add(CustomSticker(
imageFile: File(image.path),
position: Offset(150, 200),
));
});
}
}
7. 保存到相册
import 'package:image_gallery_saver/image_gallery_saver.dart';
Future<void> _saveToGallery() async {
try {
final boundary = _canvasKey.currentContext!.findRenderObject()
as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: 3.0);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
final result = await ImageGallerySaver.saveImage(
pngBytes,
quality: 100,
name: 'meme_${DateTime.now().millisecondsSinceEpoch}',
);
if (result['isSuccess']) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已保存到相册')),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('保存失败: $e')),
);
}
}
性能优化
1. 限制元素数量
void _addText() {
if (_textElements.length >= 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('文字元素不能超过10个')),
);
return;
}
// 添加文字
}
2. 使用const
const Text('文字')
const SizedBox(height: 16)
const Icon(Icons.text_fields)
3. 避免不必要的重建
// 使用RepaintBoundary隔离重绘区域
RepaintBoundary(
child: _buildCanvas(),
)
常见问题解答
Q1: 如何添加自定义字体?
A: 在pubspec.yaml中添加字体文件,然后在TextStyle中使用fontFamily属性。
Q2: 导出的图片质量如何控制?
A: 通过toImage()的pixelRatio参数控制,值越大质量越高。
Q3: 如何实现更多滤镜效果?
A: 调整ColorFilter矩阵的参数,或使用ImageFilter实现模糊等效果。
项目结构
lib/
├── main.dart # 主程序入口
├── models/
│ ├── text_element.dart # 文字元素模型
│ ├── sticker_element.dart # 贴纸元素模型
│ └── filter_type.dart # 滤镜类型
├── screens/
│ ├── editor_page.dart # 编辑器页面
│ └── template_page.dart # 模板页面
├── widgets/
│ ├── canvas_widget.dart # 画布组件
│ ├── toolbar_widget.dart # 工具栏组件
│ └── style_editor.dart # 样式编辑器
└── utils/
├── image_exporter.dart # 图片导出工具
└── filter_helper.dart # 滤镜辅助工具
总结
本文实现了一个功能完整的表情包制作器应用,涵盖了以下核心技术:
- 文字编辑:添加、编辑、样式调整
- 贴纸系统:图标贴纸的添加和管理
- 滤镜效果:ColorFilter矩阵实现多种滤镜
- 拖拽操作:GestureDetector实现元素移动
- 图片导出:RepaintBoundary捕获Widget为图片
- 分享功能:share_plus实现图片分享
- 旋转变换:Transform.rotate实现元素旋转
通过本项目,你不仅学会了如何实现表情包制作器,还掌握了Flutter中图片处理、手势识别、自定义绘制的核心技术。这些知识可以应用到更多图片编辑和创意工具的开发。
释放你的创意,制作独特的表情包!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)