尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Flutter适配鸿蒙全链路实测:从环境搭建到Canvas圆角裁剪与发布

Flutter适配鸿蒙全链路实测:从环境搭建到Canvas圆角裁剪与发布 接到鸿蒙适配需求的第一反应我是拒绝的。但我们团队的产品一直跑在 Flutter 这套跨平台方案上如果能让同一套代码直接在鸿蒙设备上跑起来那真正要做的就不是重写而是适配这个诱惑实在太大。基于这个判断我用周末时间做了一个“图片圆角制作器”应用当试验田把 Flutter 框架在鸿蒙开发环境里的完整链路走了一遍从选型、环境搭建、界面布局到 Canvas 位图裁剪、Provider 状态管理、真机打包发布每个环节都碰到了值得记录的问题。这个应用本身很简单相册里选一张图拖动滑杆调整圆角半径界面实时预览圆角效果确认后导出透明背景的圆角图片并保存到相册。功能虽然小但它把跨平台开发的典型难点全串起来了——原生能力桥接、位图渲染算法、状态共享、组件通信、平台工程配置几乎每个阶段都有坑等着你踩。这篇文章既是可复现的教程也是我的排坑记录。如果你正在评估 Flutter 鸿蒙方案能不能用或者已经进入开发阶段卡在某个环境或渲染问题上都可以直接对照着看。1. 鸿蒙化需求下的技术选型为什么我把宝押在 Flutter 上1.1 项目背景一个适合练手的图片小工具先交代一下我为什么拿“图片圆角制作器”开刀。团队手头有几个工具类产品Android 和 iOS 版本都是 Flutter 写的代码复用率已经很高。鸿蒙适配通知下来之后摆在我们面前的无非三条路第一用 ArkTS 从零重写第二等官方支持成熟再动第三尝试把现有 Flutter 工程迁到鸿蒙运行环境。选第三条路之前必须有个验证项目。我不能拿线上复杂业务直接试错万一中间有几周搞不定影响的是整个发版节奏。图片圆角器这种小工具体量正好合适——它看起来只是 UI 和图像处理的组合但实际上需要选图、预览、实时重绘、导出保存涉及到的技术点足够覆盖一套应用的核心链路。用一个周末把它的原型跑通就能大概估算出其他产品迁移的工作量。这个项目也很有代表性。圆角图片是很多社交 App、电商 App、头像裁剪工具里的基础需求市面上的“印象”“修图”类软件基本都有类似功能做出来之后本身也有实用价值不只是个空壳 Demo。我把它的完整源代码结构按照真实项目来组织后面迁移到正式产品时能直接搬代码而不是重新发明轮子。1.2 Flutter 适配鸿蒙的技术现状与可用路径先说实话现阶段 Flutter 官方主分支并没有直接合入鸿蒙支持真正能跑的是社区维护的 OpenHarmony SIG 分支包含两个关键仓库一个负责嵌入层和引擎叫 flutter_flutter是 Flutter 框架层的 fork另一个负责鸿蒙侧的渲染与系统能力对接你可以理解成 Flutter 引擎跑在鸿蒙系统上的“翻译官”。在动手之前我建议你把这两个仓库当前维护的版本号摸清楚。我自己遇到的第一个版本困惑是本机原来的 Flutter SDK 是官方 3.x 稳定版直接建工程是看不到鸿蒙平台的因为 Flutter 命令行工具本身不知道 HarmonyOS 的存在。解决办法是把 SDK 切换到社区 fork 分支然后按照它的环境检查工具链。这个过程不需要什么特殊网络操作就是正常拉代码、配环境变量只是要注意 fork 分支的版本命名和官方分支不完全一致不能想当然用 flutter --version 看到的版本号去对 API 文档。引擎渲染方面社区引擎对 Flutter 的 Impeller 渲染器支持进度一直是我比较关注的。用下来我的结论是在鸿蒙侧先把目标定为兼容稳定即可不要追新。Impeller 在 Android 和 iOS 上是未来趋势但在鸿蒙 fork 上还处于逐步适配阶段如果遇到渲染异常直接在构造项目时把渲染模式配置成兼容模式比花半天时间去查引擎代码来得实在。这块我在后面打包章节还会提到。1.3 和 ArkTS 重写路线对比我算了一笔账到底用 ArkTS 重写还是 Flutter 适配团队内部讨论过很多次。网上也经常有人问 ArkTS 和 Flutter 谁更流行我的看法是这个问题只对“从零开始做鸿蒙原生应用”有意义对已有 Flutter 产品线的团队来说判断标准应该是迁移成本而不是技术热度。假设产品有 30 个页面、50 个接口对接用 ArkTS 重写意味着 UI 层全部重来Dart 业务逻辑要翻译成 TypeScript网络层、存储层、埋点都要重新接一遍我估计至少两个人干三个月而且后续每次迭代都要双倍维护。而走 Flutter 适配路线Dart 代码几乎 100% 复用真正要动的只有原生平台通道和部分依赖鸿蒙系统能力的插件。那是不是说 ArkTS 一点优势没有也不是。如果你做的应用强依赖鸿蒙的系统级能力比如原子化服务、系统深色模式联动、多设备协同流转那用 ArkTS 走原生路线自然最顺。但普通工具类 App 的核心能力是 UI 渲染、数据存取、网络请求这些本来就是 Flutter 的强项。图片圆角制作器恰好属于这一类它不依赖任何鸿蒙专属服务所以拿它验证 Flutter 方案就显得特别合适。2. 环境搭建与工程初始化鸿蒙侧 Flutter 的准备工作和第一批坑2.1 工具链版本怎么搭配最省心环境这块我的建议是按照社区 fork 分支的 README 严格对照版本不要混合使用官方稳定版和 fork 版组件。核心工具链包含组件版本建议作用Flutter SDK社区 flutter_flutter 3.x 对应分支提供 flutter 命令与框架代码Dart SDK随 Flutter SDK 绑定编译 Dart 代码DevEco Studio5.x 及以上编译鸿蒙 HAP 包、管理签名HarmonyOS SDK与 DevEco 匹配的 API 版本提供鸿蒙系统 APIohpm随 DevEco 内置管理鸿蒙侧依赖我踩到的第一个问题很典型本机原来装了官方 Flutter 稳定版为了用 fork 分支我改了环境变量指向新目录结果flutter doctor一直识别不到 DevEco 的 SDK 路径。后来发现是缺少一步环境变量关联需要在local.properties里显式声明hwsdk.dir指向 HarmonyOS SDK 的位置Flutter 侧的命令行工具才会把鸿蒙识别为可用目标平台。这一步文档里写得不算醒目但没配好就是死活跑不了。还有个小提醒安装 DevEco Studio 之后不要急着改 IDE 设置先确认它能正常创建一个原生 HarmonyOS 工程。因为后面 Flutter 鸿蒙工程本质上是一个“Flutter 外壳 鸿蒙原生外壳”的混合工程如果原生部分本身就没跑通问题排查起来会非常痛苦很难分清是 Flutter 侧的问题还是鸿蒙侧的问题。2.2 创建工程并挂接鸿蒙平台目录不像 Android/iOS 那样flutter create之后目录里自动就有android/和ios/文件夹Flutter 工程默认不会创建鸿蒙平台目录这也是最容易让新手懵的地方。我当时拿到 fork 分支后执行完flutter create image_rounder愣了一下怎么没有 harmony 目录社区的做法是提供一个工具脚本或者在工程里手动创建harmony/目录目录里放鸿蒙侧的entry模块、build-profile.json5、oh-package.json5这些配置。结构上你可以理解成lib/下是 Flutter 应用层harmony/下是鸿蒙壳工程壳负责把 Flutter 引擎跑起来并承载页面。创建完工程后需要在pubspec.yaml里引入鸿蒙插件适配包。我当时把image_picker_ohos、path_provider_ohos这类带鸿蒙前缀的插件列进去一开始版本号写错了导致flutter pub get报找不到包。这里建议大家先确认依赖的版本是否支持你当前用的 fork 分支不要贪新选择别人已经在真机上验证过的组合最稳妥。挂接完成之后可以用flutter devices验证鸿蒙真机是否被识别。不过这一步容易遇到端口和认证问题我会放到真机调试章节详细说这里先跳过。2.3 首次构建必踩的 Gradle 与引擎报错首次构建我是跑在模拟器上的。本以为环境配好了就一路畅通结果第一个命令就给我上了一课报错信息大概是you are applying flutters main gradle plugin imperatively using the apply s...我查了一圈问题出在鸿蒙壳工程里的 Gradle 脚本写法。Flutter 插件在较新版本里对 Groovy 和 Kotlin DSL 的加载方式有强制要求社区模板如果还在用老式的apply写法就会和新版 Flutter 插件机制冲突。解决方法也很直接检查 harmony 模块的build.gradle把 Flutter 插件改成插件闭包声明方式或者反过来降级到模板推荐的 Flutter 版本让两边版本对齐。另一个高频问题是“flutter 新建项目后跑不起来”。我自己也碰到过现象是构建成功但一启动就闪退Hilog 日志里看到引擎初始化失败。排查下来原因是鸿蒙侧的引擎 so 库版本和 Flutter 框架版本不一致通常是拉取 fork 分支时用了重新编译的引擎但壳工程的依赖还是旧版。处理方式是重新执行一次依赖同步确保引擎层的版本号和flutter_flutter分支的版本号完全一致。这一步给我的教训是环境问题大多数不是“你操作不对”而是“版本组合不对”。所以你在照着教程走时遇到奇奇怪怪的构建失败先去核对版本号别先把代码翻个底朝天。3. 图片圆角制作器界面布局、实时预览与交互细节3.1 界面拆解预览区、参数区、操作区整个界面我设计了三个区域从上到下依次是图片预览区、圆角参数调节区、操作按钮区。预览区占屏幕绝大部分用Expanded撑开参数区是一个Slider加一个当前圆角数值的Text操作区是两个按钮一个选图一个保存。页面主体结构大致是这样Column( children: [ Expanded( child: Container( color: const Color(0xFF1A1A1A), alignment: Alignment.center, child: _buildPreview(), ), ), _buildRadiusPanel(), _buildActionButtons(), ], )_buildPreview()根据当前是否加载了图片决定显示提示文案还是圆角预览图。这个判断在真实项目中必须做因为用户第一次进来时图片对象是空的直接渲染会空指针。参数区除了滑杆我还加了一个“恢复默认”的小按钮把圆角重置到初始值。这是我在真实使用中顺手加的需求——预览过程中调来调去很多时候想快速回到初始状态总不至于每次都手动拖回去。3.2 圆角实时预览的实现方案实时预览我用的是 Flutter 自带的ClipRRect。它可以把子组件裁剪成圆角矩形用法很简单ClipRRect( borderRadius: BorderRadius.circular(radius), child: Image( image: AssetImage(assets/demo.jpg), fit: BoxFit.cover, width: 320, height: 320, ), )滑杆的onChanged回调里我只更新状态变量让ClipRRect的borderRadius跟着变化。由于这是纯 Flutter 组件属性更新不涉及重新加载图片性能非常稳定。即使快速拖动滑杆预览区的刷新也完全跟得上没有出现卡顿或掉帧。预览区的图片我用的是固定宽高 320 的正方形。为什么不是铺满屏幕因为圆角大小是相对图片尺寸的如果用一张窄长图做预览100 的圆角可能已经接近半圆形而用正方形视觉上更直观也方便用户估算导出的实际效果。真机使用时如果用户选的图片是长条形我会在预览区外额外提醒一句“导出效果以预览框比例为准”避免预期偏差。3.3 为什么预览用的裁剪和导出用的裁剪不是一回事这是我最想单独拎出来说的一点预览层的ClipRRect和导出层的 Canvas 裁剪完全是两套东西。ClipRRect的本质是渲染阶段的剪切它只改变“这张图在屏幕上怎么显示”并不会生成一张新的位图。文档里有个类比我记得很清楚——它像在照片上放了一个圆角相框照片本身还是方的只是被框挡住了一部分。而你导出保存到相册的必须是一张真正经历了像素级裁剪的图片也就是说背景变成了透明圆角外的像素被去掉。这是两个完全不兼容的目标。很多第一次做这个功能的同学会把两者混为一谈以为把ClipRRect包装的 Widget 截图导出就行。真要这么做一是会带上一堆界面装饰二是性能极差三是透明背景无法保留。正确做法是预览层用ClipRRect保证交互轻快导出层用 Canvas 重绘生成新位图两层逻辑分开代码结构反而更清晰。这在第四章会详细写。4. Canvas 位图裁剪与相册导出核心功能的正确打开方式4.1 鸿蒙上获取相册图片的桥接思路Flutter 生态里的image_picker插件在鸿蒙上有社区适配版本但如果你不想依赖第三方实现或者需要更精细控制相册行为用平台通道自己写一个桥接也不复杂。我们的产品里本来就定义了统一选图接口所以这个项目里我直接选择了 MethodChannel 方案。Dart 侧的核心代码static const MethodChannel _pickerChannel MethodChannel(com.image_rounder/picker); FutureString? pickImageFromAlbum() async { try { final String? path await _pickerChannel.invokeMethodString(pickImage); return path; } on PlatformException catch (e) { debugPrint(选图失败: ${e.message}); return null; } }鸿蒙侧则是在 UIAbility 或 Page 中用系统能力拉起 PhotoViewPicker。这套方案的好处是不依赖 Flutter 插件的迭代节奏系统弹相册的体验和原生完全一致。实测下来从相册选一张图片返回后把路径交给 Flutter 侧使用decodeImageFromList解码成ui.Image整个过程在鸿蒙真机上运行稳定。值得注意的一点是用户授权。新版鸿蒙相册权限申请方式有调整如果你是按照网上旧教程写的权限代码很可能会在拉起相册时看不到任何响应。遇到这种情况先检查是否漏了权限声明再检查权限申请回调有没有在用户同意之后才触发系统相册。4.2 圆角裁剪算法实现与透明背景处理拿到ui.Image之后核心问题就变成了如何生成一张圆角外的区域完全透明的图片。我的方案是用PictureRecorderCanvas绘制。import dart:ui as ui; Futureui.Image applyRoundCorner({ required ui.Image source, required double radius, }) async { final double width source.width.toDouble(); final double height source.height.toDouble(); final ui.PictureRecorder recorder ui.PictureRecorder(); final Canvas canvas Canvas(recorder); final Rect rect Rect.fromLTWH(0, 0, width, height); final RRect rrect RRect.fromRectAndRadius( rect, Radius.circular(radius.clamp(0, width / 2)), ); // 第一步把圆角矩形区域画成不透明的白色 canvas.drawRRect( rrect, Paint()..color const Color(0xFFFFFFFF), ); // 第二步把原图绘制到画布上混合模式用 dstIn只保留与圆角区域重叠的部分 canvas.drawImageRect( source, rect, rect, Paint()..blendMode BlendMode.dstIn, ); final ui.Picture picture recorder.endRecording(); return picture.toImage(source.width, source.height); }我解释一下这两步的含义。第一步在画布上画一个圆角矩形的白色蒙版可以理解成先铺一层“底色”把形状定义出来。第二步用drawImageRect把原图画上去同时把混合模式设为dstIn。dstIn的规则是“目标内容已有底色和源内容新画的原图重叠的部分被保留”所以最终画布上只留下圆角范围内的图像圆角之外的像素变成全透明。这里有个细节radius.clamp(0, width / 2)。限制最大圆角为图片宽度的一半防止半径过大导致圆角矩形变成异形。如果图片本身是长宽不相等的矩形理论上最大半径还要按短边计算。我在模型层做了统一限制所以调用方不需要关心这些边界条件。如果你需要的是“圆角边框”的效果可以在drawRRect之后再加一次drawRRect画笔换成空心线框绘制。我当时加了一个可选参数borderWidth和borderColor默认是 0 表示不加边框但保留扩展能力。4.3 导出高清图并写入系统相册picture.toImage返回的是ui.Image需要转成字节才能写入文件和相册。这里有两条路线一是用toByteData(format: ui.ImageByteFormat.png)二是先把ui.Image保存到临时路径再调用系统媒体库刷新。我建议用第二种因为大图转 PDF 字节流时容易撑爆内存。保存到临时目录我用了path_provider系插件代码套路比较固定final Directory tempDir await getTemporaryDirectory(); final String outputPath ${tempDir.path}/rounded_${DateTime.now().millisecondsSinceEpoch}.png; final File file File(outputPath); await file.writeAsBytes(bytes); // 调用系统相册保存能力 await _saveToGallery(file.path);在鸿蒙上把文件写入系统相册也是通过平台通道完成的逻辑和选图是一致的。鸿蒙侧拿到路径后调用媒体库的接口完成“相册可见”的操作。如果导出的图片在文件管理器里能看到但在系统相册里一直不出现多半是没有调用媒体库刷新这一步容易漏。性能和内存方面高分辨率图片直接在主 isolate 里做 Canvas 重绘拖动滑杆时每帧都重绘一定会有卡顿感。我的做法是预览阶段不做 Canvas 重绘只有点击“保存”时才执行一次applyRoundCorner并且通过compute把它放到 background isolate。这样主线程完全不会被阻塞导出的过程还有个 loading 遮罩体验会好很多。这里再强调一遍——实时预览的清真感靠的是ClipRRect千万不要图省事把 Canvas 重绘绑到onChanged上。5. 用 Provider 管理状态圆角参数与图片对象的联动5.1 小应用里为什么还要上状态管理看到这里你可能会问这个应用页面就一个用setState完全能搞定为什么还要引入 Provider我的理由主要有三个。第一图片圆角制作器虽然只有一个页面但导出功能会开一个 loading 层后续我还打算加“历史记录”页面这些页面之间要共享同一个图片对象和圆角参数。如果靠setState逐级回调代码会越写越绕而 Provider 天生就是解决跨组件共享状态的。第二状态管理在这里相当于给项目一个“扩展位”。我最初确实是想用setState糊完拉倒但做产品的人都知道这种小功能最容易不断加需求——加滤镜、加贴纸、加文字水印都要动状态层。在 Flutter 里选状态管理方案本质是在选未来项目架构的骨架这一点在鸿蒙跨平台项目里同样成立。第三Flutter 组件通信一直是初学者最容易懵的话题Provider恰好是理解“父层级状态如何传递给深层子组件”的最好入口。它底层的InheritedWidget机制并不玄乎学会了 Provider顺带也就理解了 Flutter 的依赖注入思路。后面团队新人接手项目看到一套清晰的状态层也能快速上手。5.2 基于 ChangeNotifier 的数据模型我用一个ChangeNotifier子类承载全部业务状态定义如下import dart:ui as ui; import package:flutter/foundation.dart; class RounderState extends ChangeNotifier { ui.Image? _sourceImage; double _radius 32; bool _saving false; ui.Image? get sourceImage _sourceImage; double get radius _radius; bool get saving _saving; void updateImage(ui.Image image) { _sourceImage image; notifyListeners(); } void updateRadius(double value) { _radius value.clamp(0, 180); notifyListeners(); } void setSaving(bool value) { _saving value; notifyListeners(); } }这里我把圆角半径的合法范围限制在 0 到 180这只是 UI 层面的合理范围真正的尺寸兜底在 Canvas 裁剪函数里会再做一次。updateRadius里的clamp是为了防抖——如果用户在滑杆上快速拖动产生微小抖动小于 0 或大于 180 的非法值会被拦截状态始终保持一致。这里要强调一个细节图片对象本身是ui.Image属性和垃圾回收都需要小心。当用户第二次选图时应及时把旧图释放掉。我在updateImage里加了一段注释提醒自己如果_sourceImage不为空先调用之前那张图的dispose()再替换避免内存泄漏。别小看这张图手机拍出来的照片解成ui.Image动辄几十 MB反复选图不释放内存很快就上去了。5.3 MultiProvider 装配与页面监听写法在入口处我用MultiProvider把RounderState挂到组件树顶层void main() { WidgetsFlutterBinding.ensureInitialized(); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) RounderState()), ], child: const RounderApp(), ), ); }页面里读取状态我遵循“用context.watch取渲染依赖用context.read触发动作”的原则。滑杆的值依赖 radius所以用watchfinal radius context.watchRounderState().radius; Slider( min: 0, max: 180, value: radius, onChanged: (value) { context.readRounderState().updateRadius(value); }, )为什么不能全程都用watch因为watch会在依赖状态变化时重建当前 Widget。如果按钮点击时只触发saving变化页面里所有watch了RounderState的组件都会重建明明只是那个按钮要转圈整个预览区也跟着重刷白白浪费性能。用read则不会触发重建适合一次性的动作回调。实践下来这套写法结构清晰而且很好测试。因为我所有的状态都集中在RounderState里单测时可以构造一个对象直接调用updateRadius验证radius是否被正确 clamp。这种好处在项目变大之后体会更深单元测试好写改动状态逻辑时也不用东翻西找。6. 真机调试、打包签名与发布避坑记录6.1 连接鸿蒙真机调试要注意的基础配置模拟器上功能跑通之后我第一时间就想上真机。鸿蒙真机的开发者模式开启和 Android 大同小异在“关于本机”里连点版本号进入开发者选项打开 USB 调试。问题往往不在这步而是你把手机插上电脑之后flutter devices列表里根本看不到设备。我遇到的情况是换了一台电脑连上鸿蒙手机后设备一直无法识别。第一步排查是看看电脑上设备管理器里有没有出现未知设备如果没有大概率是缺少鸿蒙设备的 USB 驱动如果出现了但flutter devices不识别排查方向就要转到开发模式是否开启、是否授权这台电脑调试。另外提醒一句用 Flutter 跑鸿蒙真机时最好直接用 DevEco Studio 里的 Run 功能先验证原生存活确认 HAP 能装上、能启动再用flutter run跑全流程。因为 Flutter 侧启动鸿蒙应用会把 flutter engine 打进去安装包体积比原生 HAP 大不少首次安装和启动耗时很长不要误以为是卡死了。看到控制台输出一直停在某一行多等一下蓝颜色日志刷出来才是真的在跑。6.2 签名证书与 HAP 打包流程到发布阶段绕不开的是签名。鸿蒙应用打包 HAP 需要证书证书包括 Profile 文件和 keystore。创建证书的入口在 DevEco Studio 的 Project Structure 里需要填一些基本项目信息然后配置签名。这里有一条很容易踩的弯路直接用默认的调试证书打包应用只能在开发者设备上安装想分发给别人或者上架必须重新走正式证书流程。构建 HAP 之前先确认build-profile.json5里的签名配置有没有被正确引用。有时候你在 IDE 里设置了签名但命令行构建时会因为找不到 keystore 路径而失败。我当时直接把 keystore 放到了工程目录下并加了忽略配置避免路径问题也避免泄露钥匙库文件。Flutter 工程打包鸿蒙 HAP 的命令流程和 Android 的 assembleRelease 有区别。通常是在harmony/目录下执行鸿蒙构建命令生成以.hap结尾的产物。这个产物可以安装到鸿蒙设备上验证如果需要在应用市场发布再按平台方要求补充包信息。第一次打 HAP 的时候我盯着输出看了半天不知道构建完没完后来总结的经验就是看产物目录里有没有新增.hap文件比看终端日志更直观。6.3 我遇到的三个典型报错与处理方式第一个就是引擎初始化失败。真机启动时日志打印类似E/flutter: [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled exception...看到这类信息先别慌。这个dart_vm_initializer.cc是 Dart VM 初始化入口提示的是未捕获异常但真正的原因往往在上面的 Dart 层日志里。我当时是选图后解码流程抛了异常导致页面状态没更新然后就一直不渲染。解决方式是给decodeImageFromList包上异常捕获并返回错误提示给用户而不是让整个应用崩溃。第二个是 Gradle 插件加载方式冲突。就是章节 2.3 里提到的报错不只是首次构建会遇到有时候你加了一个新依赖构建脚本被重新生成老问题又会冒出来。解法是检查harmony/模块下的 Gradle 脚本确保 Flutter 插件声明格式和当前分支风格一致。第三个是图片显示为全黑。这是我调试导出功能时发现的预览图、列表正常但导出后保存到相册打开圆角图片全是黑的。排查了很久原因是我在 Canvas 绘制时用了不正确的颜色空间转换导致透明通道数据丢失。修法是把绘制目标明确成带 alpha 通道的位图格式并在导出前做一次像素格式校验。这个问题在文档里很难找到答案只能靠断点输出每个绘制步骤的图片信息逐步缩小范围。每一次踩坑我都记录在项目的 docs 目录下包括复现步骤、日志截图、解决方式。团队里其他成员如果碰到同样的问题直接搜文档关键字就行不用重新经历一遍排查链路。这也是我愿意把所有问题梳理成文的原因——跨平台开发看着很美好但真正的成本都藏在文档之外的这些角落。最后再分享一个小技巧图片圆角器的滑杆我用的是 0 到 180但不同图片的最佳圆角区间差异很大。头像类的小图圆角 30 左右就很好看封面类的大图圆角 80 以上才有明显的“卡片感”。你平时自己用的时候可以先拖到中间值再微调感受会直观很多。如果后续想做得更贴心可以加一个“按图片尺寸自动计算推荐圆角”的逻辑这也是我下一版准备做的事。
返回列表