Flutter 框架跨平台鸿蒙开发 - 景观设计工具
运行效果图元素类型英文标识图标默认颜色应用场景树木treepark绿色乔木种植灌木shrubgrass浅绿色绿篱灌木花卉flower粉色花坛花境草坪grasslandscape浅绿色草地铺装水池waterwater蓝色水景设计石头stonetexture灰色置石造景小路pathroute棕色园路设计建筑buildinghome蓝灰色建筑布局围栏fencefence棕色边界围合路灯light。
Flutter景观设计工具
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
项目概述
运行效果图



一、项目背景与目标
景观设计是一门融合艺术与科学的综合性学科,涉及园林规划、植物配置、空间布局等多个领域。传统的景观设计依赖于专业软件和手绘草图,学习门槛较高。本项目基于Flutter框架开发一款景观设计工具,旨在降低景观设计的入门门槛,让普通用户也能轻松创建专业的景观设计方案。
项目的核心目标涵盖多个维度:构建完整的景观元素库,实现直观的拖拽编辑,设计专业的绘制引擎,打造流畅的用户体验,以及确保应用的稳定性和性能表现。通过本项目的开发,不仅能够深入理解Flutter在图形编辑类应用中的应用,更能掌握自定义绘制、手势交互、状态管理等核心技术要点。
二、技术选型与架构设计
技术栈分析
本项目选用Flutter作为开发框架,主要基于以下考量:Flutter的Skia渲染引擎提供了强大的图形处理能力,非常适合图形编辑类应用;声明式UI编程范式能够高效构建复杂的编辑界面;热重载功能大幅提升了开发调试效率;丰富的Widget组件库为应用UI开发提供了坚实基础。
Dart语言作为Flutter的开发语言,具备强类型、异步编程支持、优秀的性能表现等特性。项目采用单文件架构,将所有应用逻辑集中在main.dart文件中,这种设计既便于代码管理,又利于理解应用整体架构。
架构层次划分
应用架构采用分层设计思想,主要分为以下几个层次:
数据模型层:定义应用中的核心数据结构,包括LandscapeElement(景观元素)、LandscapeProject(景观项目)、ElementType(元素类型)等类和枚举。这些模型类封装了景观设计的状态和行为,构成了应用逻辑的基础。
业务逻辑层:实现应用的核心功能逻辑,包括元素添加、选择、移动、删除、图层管理等。这一层是应用的心脏,决定了应用的功能性和可用性。
渲染表现层:负责应用界面的绘制和UI展示,使用Flutter的Material Design组件库实现现代化的界面设计,通过CustomPaint组件实现景观元素的实时渲染。
状态管理层:管理应用的各种状态,包括当前项目、选中元素、工具索引、缩放级别等,确保应用状态的一致性和可预测性。
核心功能模块详解
一、景观元素系统
元素类型定义
应用提供十二种景观元素类型,涵盖景观设计的主要元素:
| 元素类型 | 英文标识 | 图标 | 默认颜色 | 应用场景 |
|---|---|---|---|---|
| 树木 | tree | park | 绿色 | 乔木种植 |
| 灌木 | shrub | grass | 浅绿色 | 绿篱灌木 |
| 花卉 | flower | local_florist | 粉色 | 花坛花境 |
| 草坪 | grass | landscape | 浅绿色 | 草地铺装 |
| 水池 | water | water | 蓝色 | 水景设计 |
| 石头 | stone | texture | 灰色 | 置石造景 |
| 小路 | path | route | 棕色 | 园路设计 |
| 建筑 | building | home | 蓝灰色 | 建筑布局 |
| 围栏 | fence | fence | 棕色 | 边界围合 |
| 路灯 | light | light_mode | 琥珀色 | 照明设计 |
| 长椅 | bench | chair | 橙色 | 休憩设施 |
| 凉亭 | gazebo | temple_buddhist | 青色 | 景观构筑 |
元素数据模型
景观元素封装了位置、尺寸、变换等属性:
class LandscapeElement {
String id;
ElementType type;
double x;
double y;
double width;
double height;
double rotation;
double scale;
String? label;
Color? customColor;
}
元素ID使用时间戳生成,确保唯一性;位置使用画布坐标,支持精确到像素的定位;宽高定义元素的边界框,用于碰撞检测;旋转角度以度为单位,支持0-360度旋转;缩放比例支持0.5-3.0倍缩放;标签属性允许用户为元素添加文字标注;自定义颜色允许用户修改元素的默认颜色。
元素绘制实现
每种元素类型都有对应的绘制方法,使用Canvas API实现矢量图形绘制:
树木绘制:树木由树干和树冠两部分组成,树干使用棕色矩形,树冠使用绿色三角形叠加:
void _drawTree(Canvas canvas, Paint paint, Paint strokePaint, LandscapeElement element) {
paint.color = Colors.brown;
canvas.drawRect(const Rect.fromLTWH(-5, 0, 10, 25), paint);
paint.color = element.customColor ?? Colors.green.shade700;
final path = Path();
path.moveTo(0, -30);
path.lineTo(-20, 5);
path.lineTo(20, 5);
path.close();
canvas.drawPath(path, paint);
path.reset();
path.moveTo(0, -20);
path.lineTo(-15, 10);
path.lineTo(15, 10);
path.close();
canvas.drawPath(path, paint);
}
花卉绘制:花卉由茎干和花瓣组成,花瓣使用圆形围绕中心排列:
void _drawFlower(Canvas canvas, Paint paint, Paint strokePaint, LandscapeElement element) {
paint.color = Colors.green;
canvas.drawRect(const Rect.fromLTWH(-1, 0, 2, 15), paint);
paint.color = element.customColor ?? Colors.pink;
for (int i = 0; i < 5; i++) {
final angle = i * 72 * math.pi / 180;
canvas.drawCircle(Offset(8 * math.cos(angle), 8 * math.sin(angle) - 5), 5, paint);
}
paint.color = Colors.yellow;
canvas.drawCircle(const Offset(0, -5), 4, paint);
}
水池绘制:水池使用椭圆形表示,添加波纹线条增强视觉效果:
void _drawWater(Canvas canvas, Paint paint, Paint strokePaint, LandscapeElement element) {
paint.color = element.customColor ?? Colors.blue.shade300;
canvas.drawOval(const Rect.fromLTWH(-25, -15, 50, 30), paint);
strokePaint.color = Colors.blue.shade200;
strokePaint.strokeWidth = 1;
canvas.drawLine(const Offset(-15, 0), const Offset(-5, 0), strokePaint);
canvas.drawLine(const Offset(5, -5), const Offset(15, -5), strokePaint);
canvas.drawLine(const Offset(-10, 5), const Offset(0, 5), strokePaint);
}
二、画布编辑系统
画布坐标系统
画布采用笛卡尔坐标系,原点位于左上角,X轴向右为正,Y轴向下为正。元素位置使用画布坐标,支持缩放和平移变换:
void _onCanvasTap(TapDownDetails details, Size canvasSize) {
final localPosition = (details.localPosition - _offset) / _zoom;
for (var element in widget.project.elements.reversed) {
if (_isPointInElement(localPosition, element)) {
setState(() => _selectedElement = element);
return;
}
}
}
点击位置需要经过逆变换,从屏幕坐标转换为画布坐标,才能正确判断点击了哪个元素。
网格吸附功能
网格吸附帮助用户精确对齐元素:
final snappedPosition = _snapToGrid
? Offset(
(localPosition.dx / _gridSize).round() * _gridSize,
(localPosition.dy / _gridSize).round() * _gridSize,
)
: localPosition;
网格大小默认为20像素,吸附时将坐标四舍五入到最近的网格交点。网格吸附可以开关,满足不同精度的设计需求。
元素拖拽移动
元素支持拖拽移动,实时更新位置:
onPanUpdate: (details) {
if (_selectedElement != null && _toolIndex == 0) {
setState(() {
_selectedElement!.x += details.delta.dx / _zoom;
_selectedElement!.y += details.delta.dy / _zoom;
if (_snapToGrid) {
_selectedElement!.x = (_selectedElement!.x / _gridSize).round() * _gridSize;
_selectedElement!.y = (_selectedElement!.y / _gridSize).round() * _gridSize;
}
});
}
},
拖拽时需要考虑缩放比例,移动距离需要除以缩放比例才能得到正确的画布位移。如果开启了网格吸附,移动后自动吸附到最近的网格交点。
元素碰撞检测
点击检测使用矩形碰撞算法:
bool _isPointInElement(Offset point, LandscapeElement element) {
final halfWidth = element.width * element.scale / 2;
final halfHeight = element.height * element.scale / 2;
return point.dx >= element.x - halfWidth &&
point.dx <= element.x + halfWidth &&
point.dy >= element.y - halfHeight &&
point.dy <= element.y + halfHeight;
}
元素边界框以中心点为基准,宽高需要乘以缩放比例。点击坐标在边界框内即判定为命中。
三、图层管理系统
图层顺序控制
元素按照添加顺序渲染,后添加的元素显示在上层:
for (var element in elements) {
_drawElement(canvas, element, element == selectedElement);
}
图层管理提供置于顶层和置于底层功能:
void _bringToFront(LandscapeElement element) {
setState(() {
widget.project.elements.remove(element);
widget.project.elements.add(element);
});
}
void _sendToBack(LandscapeElement element) {
setState(() {
widget.project.elements.remove(element);
widget.project.elements.insert(0, element);
});
}
置于顶层将元素移动到列表末尾,置于底层将元素移动到列表开头。列表顺序即为渲染顺序。
元素复制功能
元素复制创建完全相同的副本,位置偏移避免重叠:
void _duplicateElement(LandscapeElement element) {
final newElement = LandscapeElement(
id: DateTime.now().millisecondsSinceEpoch.toString(),
type: element.type,
x: element.x + 20,
y: element.y + 20,
width: element.width,
height: element.height,
rotation: element.rotation,
scale: element.scale,
label: element.label,
customColor: element.customColor,
);
setState(() {
widget.project.elements.add(newElement);
_selectedElement = newElement;
});
}
复制后自动选中新元素,方便用户继续编辑。
四、属性编辑面板
位置编辑
位置编辑使用文本输入框,支持精确数值输入:
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(labelText: 'X', border: OutlineInputBorder()),
controller: TextEditingController(text: element.x.round().toString()),
keyboardType: TextInputType.number,
onChanged: (value) {
final v = double.tryParse(value);
if (v != null) setState(() => element.x = v);
},
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
decoration: const InputDecoration(labelText: 'Y', border: OutlineInputBorder()),
controller: TextEditingController(text: element.y.round().toString()),
keyboardType: TextInputType.number,
onChanged: (value) {
final v = double.tryParse(value);
if (v != null) setState(() => element.y = v);
},
),
),
],
)
输入框显示当前值的整数形式,支持实时更新。无效输入会被忽略,保持原有值不变。
缩放与旋转
缩放和旋转使用滑块控件,提供直观的调节体验:
const Text('缩放', style: TextStyle(fontWeight: FontWeight.bold)),
Slider(
value: element.scale,
min: 0.5,
max: 3.0,
divisions: 25,
label: element.scale.toStringAsFixed(1),
onChanged: (value) => setState(() => element.scale = value),
),
const SizedBox(height: 8),
const Text('旋转', style: TextStyle(fontWeight: FontWeight.bold)),
Slider(
value: element.rotation,
min: 0,
max: 360,
divisions: 36,
label: '${element.rotation.round()}°',
onChanged: (value) => setState(() => element.rotation = value),
),
缩放范围0.5-3.0,分为25档;旋转范围0-360度,分为36档,每档10度。滑块显示当前值的标签,方便用户确认。
标签编辑
标签编辑使用文本输入框,支持为元素添加文字标注:
TextField(
decoration: const InputDecoration(border: OutlineInputBorder()),
controller: TextEditingController(text: element.label ?? ''),
onChanged: (value) => setState(() => element.label = value.isEmpty ? null : value),
)
标签为空时设为null,不显示标签。标签显示在元素下方,背景为白色,确保可读性。
五、项目管理模块
项目数据模型
项目封装了画布尺寸、元素列表等属性:
class LandscapeProject {
String id;
String name;
double width;
double height;
List<LandscapeElement> elements;
DateTime createdAt;
DateTime modifiedAt;
String? backgroundPath;
}
画布尺寸默认为800×600像素,支持自定义。创建时间和修改时间用于项目排序和展示。背景路径预留了背景图片功能。
项目列表展示
项目列表使用卡片布局,每个卡片显示项目预览和基本信息:
Widget _buildProjectCard(LandscapeProject project) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () => _openProject(project),
onLongPress: () => _showProjectOptions(project),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.green.shade100,
borderRadius: BorderRadius.circular(8),
),
child: CustomPaint(
painter: ProjectPreviewPainter(project.elements),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(project.name, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text('${project.width.toInt()} × ${project.height.toInt()} | ${project.elements.length} 个元素'),
const SizedBox(height: 4),
Text('修改于 ${_formatDate(project.modifiedAt)}'),
],
),
),
Icon(Icons.chevron_right, color: Colors.grey.shade400),
],
),
),
),
);
}
项目预览使用CustomPaint绘制元素缩略图,直观展示项目内容。
项目预览绘制
项目预览将元素缩小绘制到80×80的区域内:
class ProjectPreviewPainter extends CustomPainter {
final List<LandscapeElement> elements;
ProjectPreviewPainter(this.elements);
void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.fill;
for (var element in elements) {
paint.color = element.defaultColor.withOpacity(0.7);
canvas.drawCircle(
Offset(element.x / 10, element.y / 10),
8,
paint,
);
}
}
}
预览将元素位置缩小10倍,用圆形表示元素位置和类型颜色,提供快速预览效果。
UI界面开发
一、主界面布局
主界面采用底部导航栏设计,包含三个主要页面:
BottomNavigationBar(
currentIndex: _currentIndex > 2 ? 0 : _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
selectedItemColor: Colors.green,
unselectedItemColor: Colors.grey,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.folder), label: '项目'),
BottomNavigationBarItem(icon: Icon(Icons.view_quilt), label: '模板'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
],
)
底部导航栏使用绿色作为选中颜色,与景观设计的主题相符。三个页面分别是项目列表、设计模板和设置页面,覆盖了应用的主要功能入口。
二、编辑器界面
编辑器界面分为左中右三栏布局:
Scaffold(
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: widget.onBack),
title: Text(widget.project.name),
actions: [
IconButton(icon: Icon(_showGrid ? Icons.grid_on : Icons.grid_off), onPressed: () => setState(() => _showGrid = !_showGrid)),
IconButton(icon: Icon(_snapToGrid ? Icons.snap_to_pixel : Icons.snap_outlined), onPressed: () => setState(() => _snapToGrid = !_snapToGrid)),
IconButton(icon: const Icon(Icons.undo), onPressed: () {}),
IconButton(icon: const Icon(Icons.redo), onPressed: () {}),
PopupMenuButton<String>(...),
],
),
body: Row(
children: [
_buildToolPanel(),
Expanded(child: _buildCanvas()),
if (_selectedElement != null) _buildPropertyPanel(),
],
),
)
应用栏提供返回、网格开关、吸附开关、撤销、重做、导出等操作按钮。左侧工具面板宽度80像素,提供元素选择工具;中间画布区域自适应宽度,支持缩放和平移;右侧属性面板宽度240像素,选中元素时显示。
三、工具面板设计
工具面板采用垂直列表布局,顶部为选择工具,下方为元素工具:
Widget _buildToolPanel() {
return Container(
width: 80,
color: Colors.grey.shade200,
child: Column(
children: [
_buildToolButton(0, Icons.pan_tool, '选择'),
const Divider(height: 1),
Expanded(
child: ListView.builder(
itemCount: _elementTools.length,
itemBuilder: (context, index) {
final tool = _elementTools[index];
return _buildToolButton(index + 1, tool['icon'], tool['name']);
},
),
),
],
),
);
}
工具按钮显示图标和名称,选中状态以绿色背景标识。选择工具用于选择和移动元素,元素工具用于添加对应类型的元素。
四、画布渲染实现
画布使用InteractiveViewer组件实现缩放和平移:
InteractiveViewer(
minScale: 0.25,
maxScale: 4.0,
onInteractionUpdate: (details) {
setState(() {
_zoom = details.scale;
_offset = details.focalPoint;
});
},
child: Container(
width: widget.project.width,
height: widget.project.height,
color: Colors.white,
child: CustomPaint(
painter: LandscapeCanvasPainter(
elements: widget.project.elements,
selectedElement: _selectedElement,
showGrid: _showGrid,
gridSize: _gridSize,
),
),
),
)
InteractiveViewer支持双指缩放和拖拽平移,缩放范围0.25-4.0倍。画布使用CustomPaint绘制网格和元素,通过LandscapeCanvasPainter实现自定义绘制。
性能优化方案
一、绘制优化
画布绘制采用增量更新策略,只在元素变化时重绘:
bool shouldRepaint(covariant LandscapeCanvasPainter oldDelegate) {
return elements != oldDelegate.elements ||
selectedElement != oldDelegate.selectedElement ||
showGrid != oldDelegate.showGrid;
}
shouldRepaint方法比较新旧状态,只有状态变化时才触发重绘,避免不必要的渲染开销。
二、状态管理优化
应用状态采用setState()方法管理,确保状态的一致性和可预测性:
setState(() {
_selectedElement!.x += details.delta.dx / _zoom;
_selectedElement!.y += details.delta.dy / _zoom;
});
状态更新批量执行,减少了不必要的重绘次数。对于复杂的状态管理,可以考虑使用Provider、Riverpod等状态管理方案。
三、列表渲染优化
项目列表和工具列表采用ListView.builder组件,实现了按需渲染:
ListView.builder(
itemCount: _elementTools.length,
itemBuilder: (context, index) {
final tool = _elementTools[index];
return _buildToolButton(index + 1, tool['icon'], tool['name']);
},
)
只有可见区域的项目才会被创建和渲染,大幅降低了内存占用和渲染开销。
测试方案与步骤
一、功能测试
功能测试旨在验证应用各项功能是否按预期工作。测试用例应覆盖所有核心功能模块,确保应用逻辑的正确性。
元素操作测试:验证元素添加、选择、移动、删除功能是否正常;测试元素复制、图层调整功能是否正确;检查元素属性编辑是否生效。
画布操作测试:验证画布缩放、平移功能是否正常;测试网格显示、网格吸附功能是否正确;检查元素碰撞检测是否准确。
项目管理测试:验证项目创建、重命名、复制、删除功能是否正常;测试项目列表展示是否正确;检查项目预览是否准确。
二、性能测试
性能测试关注应用的运行效率,确保在各种情况下都能流畅运行。
大量元素测试:测试画布包含大量元素时的渲染性能,确保拖拽流畅。
缩放性能测试:测试不同缩放级别下的渲染性能,确保无卡顿。
内存占用测试:监测应用运行过程中的内存使用情况,确保没有内存泄漏。
三、兼容性测试
兼容性测试确保应用在不同环境下都能正常运行。
多平台测试:在Android、iOS等平台分别测试应用功能,验证跨平台一致性。
屏幕适配测试:测试应用在不同屏幕尺寸下的表现,确保布局正确。
横竖屏测试:测试应用在横竖屏切换时的表现,确保界面正常。
四、用户体验测试
用户体验测试关注应用的易用性和美观度。
操作便捷性测试:邀请用户试用应用,收集对操作流程的反馈,评估交互设计的合理性。
视觉体验测试:评估应用的视觉效果,包括色彩搭配、图标设计、布局美观度等。
响应速度测试:测试应用的响应速度,确保操作反馈及时,提升用户体验。
项目总结与展望
一、项目成果总结
本项目成功实现了一款功能完整、界面专业的景观设计工具,涵盖了图形编辑类应用开发的核心要素。通过Flutter框架的应用,实现了跨平台的应用体验,证明了Flutter在图形编辑类应用开发领域的可行性。
项目采用模块化设计思想,将应用功能划分为元素系统、画布编辑、图层管理、属性编辑、项目管理等独立模块,各模块职责明确,耦合度低,便于维护和扩展。
代码实现注重性能优化和用户体验,通过自定义绘制、增量更新、状态管理等手段,确保了应用在各种情况下的流畅运行。
二、技术亮点总结
矢量图形绘制:使用Canvas API实现了十二种景观元素的矢量绘制,支持缩放和旋转而不失真。
画布交互系统:实现了完整的画布交互,包括缩放、平移、网格吸附、元素拖拽等功能。
图层管理系统:实现了图层顺序控制,支持置于顶层、置于底层等操作。
属性编辑面板:实现了完整的属性编辑,包括位置、尺寸、缩放、旋转、标签等属性。
项目预览功能:实现了项目缩略图预览,直观展示项目内容。
三、未来优化方向
更多元素类型:增加更多景观元素类型,如喷泉、雕塑、花架、景墙等,丰富元素库。
元素组合功能:支持元素组合,将多个元素组合为一个整体,便于移动和复制。
撤销重做功能:实现完整的撤销重做功能,记录用户的每一步操作。
背景图片功能:支持导入背景图片,在真实场景上进行景观设计。
导出高清图片:实现高清图片导出,支持指定分辨率和格式。
云同步功能:实现项目云同步,支持多设备协作。
测量标注功能:实现距离测量和尺寸标注,提供专业的设计辅助。
植物数据库:集成植物数据库,提供植物习性、养护信息等专业知识。
四、开发经验总结
通过本项目的开发,积累了宝贵的Flutter应用开发经验:
自定义绘制的重要性:图形编辑类应用的核心是自定义绘制,理解Canvas API、Path、Paint等概念,是开发此类应用的基础。
交互设计的核心地位:编辑类应用最终服务于用户,交互设计是评判应用质量的核心标准。从元素的选中反馈到属性的实时更新,每个细节都需要精心打磨。
性能优化的持续性:性能优化不是一次性工作,需要在开发过程中持续关注,通过性能分析工具定位瓶颈,针对性优化。
跨平台兼容性的挑战:跨平台开发需要考虑不同平台的差异,包括屏幕尺寸、像素密度、触摸精度等,确保应用在各平台上的表现一致。
本项目为Flutter图形编辑应用开发提供了一个完整的实践案例,展示了如何实现矢量绘制、画布交互、图层管理等核心功能,希望能够为相关开发者提供参考和启发,推动Flutter在专业工具类应用开发领域的应用和发展。
更多推荐



所有评论(0)