
引子转完转盘用户还想看什么第一版的转盘页面转完之后只显示两行字——“大吉·白羊座和今日关键词热情勇敢”。用户看一眼就没了没有继续探索的动力。优化后加了运势详解爱情、事业、健康、财运四个维度每个带星级评分和一段解读。底下还加了今日幸运物——幸运色、守护石、吉时、方位。转完转盘后用户会往下滚动看详情停留时间明显变长。完整效果数据结构运势结果的建模先看数据结构。运势结果需要三个核心元素运势等级、四维评分、幸运物品。// 运势等级constFORTUNE_LABELS:string[][大吉,中吉,小吉,末吉,大吉,中吉,小吉,大吉,中吉,末吉,小吉,中吉];// 四维评分数据interfaceFortuneDetail{category:string;// 维度名称emoji:string;// 图标stars:number;// 星级1-5text:string;// 解读文本}// 幸运物数据interfaceLuckyItem{emoji:string;label:string;value:string;}为什么用 interface 而不是 class运势数据是只读的不需要实例化方法用interface比class更轻量。interface编译后不会生成额外的 JavaScript 代码包体积更小。为什么四维数据用 interface 数组如果用四个独立变量loveStars、careerStars、healthStars、wealthStars代码会很冗长而且ForEach无法复用。用FortuneDetail[]数组可以用循环渲染代码更简洁。数据和 UI 的分离运势数据定义在model/DataModel.ets里UI 渲染在view/WheelView.ets里。修改数据不需要改 UI修改 UI 不会影响数据。这种分离是模块化的核心思想。星级评分满星、半星、空星的视觉表达星级评分是这个页面最有意思的部分——用 emoji 字符组合出满星、半星、空星的效果。渲染逻辑// 满星Text(★).fontSize(11).fontColor(#FFD700)// 半星Text(☆).fontSize(11).fontColor(#FFD700)// 空星Text(☆).fontSize(11).fontColor(#3B2F78)满星用金色实心星★半星用金色空心星☆空星用深色空心星☆。通过颜色区分有和无而不是用不同的字符。循环渲染的实现Row(){ForEach([1,2,3,4,5],(n:number){if(nstars){// 满星Text(★).fontSize(11).fontColor(#FFD700)}elseif(n-0.5stars){// 半星Text(☆).fontSize(11).fontColor(#FFD700)}else{// 空星Text(☆).fontSize(11).fontColor(#3B2F78)}})}遍历 1 到 5根据当前序号和星级的关系决定显示哪种星n stars满星比如 stars4n1/2/3/4 都是满星n - 0.5 stars半星比如 stars4.5n5 时 5-0.54.5 4.5显示半星其他空星为什么用 emoji 而不是自定义组件用Text组件加背景色也能实现星星效果比如用linearGradient做半星但 emoji 的优势在于代码简单一个Text组件就行不需要额外的样式代码性能好emoji 是字体渲染比自定义组件快兼容性emoji 在所有设备上都能正常显示缺点是半星的视觉效果不如自定义组件精确——emoji 的半星只是空心星不是真正的一半实心一半空心。但对于运势应用来说用户不会纠结半星的精确度。为什么不用 0.5 步进有些评分系统用 0.5 步进比如 4.5 分但这个应用用整数步进1-5 星。原因是运势评分是主观的不需要精确到半星。用整数更简洁用户也更容易理解。卡片式布局四个维度的排列运势详解的四个维度用卡片式布局排列——每个维度一个独立卡片包含图标、标题、星级和解读文本。卡片的结构ForEach(fortuneDetails,(item:FortuneDetail){Column(){Row(){// 第一行图标 标题 星级Text(item.emoji).fontSize(14)Text(item.category).fontSize(13).fontWeight(FontWeight.Bold).fontColor(#E8DFF8).margin({left:6})Blank()// 弹性空白把星级推到右边// 星级渲染上面讲的 ForEach 逻辑}.width(100%)// 第二行解读文本Text(item.text).fontSize(11).fontColor(#BBAAE0).lineHeight(18).margin({top:6})}.width(100%).padding(12).backgroundColor(#0E0B25).borderRadius(10).border({width:0.5,color:#1C1545,style:BorderStyle.Solid}).margin({bottom:8})})为什么用 Row 嵌套而不是 Grid四维卡片可以用 Grid 的两列两行布局但 Row 嵌套更灵活高度自适应当某个卡片的解读文本特别长时卡片高度会自动撑开不会出现 Grid 布局中高度不一致的问题响应式在大屏设备上卡片可以保持全宽而不是被 Grid 强制分成两列代码简单不需要计算 Grid 的列宽和行高Blank() 的作用Blank()是 ArkUI 的弹性空白组件会自动填充剩余空间。在 Row 里用Blank()可以让左边的图标标题和右边的星级分开不需要手动计算间距。Row(){Text(item.emoji)// 左边图标Text(item.category)// 左边标题Blank()// 中间弹性空白// 星级 // 右边星级}如果不加Blank()图标、标题、星级会挤在一起。如果用margin({ left: auto })也能实现右对齐但Blank()更语义化。间距和对齐卡片之间margin({ bottom: 8 })留 8px 间距卡片内部padding(12)留 12px 内边距标题和星级Row水平排列Blank()分隔解读文本margin({ top: 6 })和标题行留 6px 间距背景色和边框背景色#0E0B25深紫色和页面背景#06060F有区分边框0.5px #1C1545极细的边框增加层次感但不抢眼圆角borderRadius(10)柔和的圆角避免直角的生硬幸运物四个小方块的实现幸运物区域用四个小方块并排展示每个方块包含 emoji 图标、标题和具体内容。数据结构constLUCKY_ITEMS:LuckyItem[][{emoji:,label:幸运色,value:蓝色},{emoji:,label:守护石,value:青金石},{emoji:,label:吉时,value:下午4点},{emoji:,label:方位,value:北方},];布局实现Row(){ForEach(LUCKY_ITEMS,(item:LuckyItem){Column(){Text(item.emoji).fontSize(20)Text(item.label).fontSize(9).fontColor(#6B5B9A).margin({top:4})Text(item.value).fontSize(11).fontWeight(FontWeight.Bold).fontColor(#E8DFF8).margin({top:2})}.width(22%).padding({top:8,bottom:8}).backgroundColor(#0E0B25).borderRadius(8).border({width:0.5,color:#1C1545,style:BorderStyle.Solid}).margin({left:4,right:4})})}.width(100%).justifyContent(FlexAlign.Center)为什么用 22% 而不是 25%四个方块如果用25%宽度加上 margin 的间距总宽度会超过 100%导致换行。用22%留出余量确保四个方块在同一行。居中对齐.justifyContent(FlexAlign.Center)让四个方块在屏幕中间排列而不是靠左。这是因为幸运物是附加信息居中排列更符合视觉习惯。三个文字的层次每个方块里有三个文字元素emojifontSize(20)最大视觉焦点标题fontSize(9)最小#6B5B9A暗色弱化内容fontSize(11)中等#E8DFF8亮色突出这种大-小-中的文字层次让用户先看到 emoji吸引注意力再看到内容获取信息最后看到标题辅助理解。数据映射从数据到 UI 的完整流程整个运势详解的数据流是转盘停止 → 计算星座索引 (resultIdx) ↓ 从 SIGNS 数组获取星座数据 ↓ 从 FORTUNE_LABELS 获取运势等级 ↓ 从 fortuneDetails 获取四维评分 ↓ 从 LUCKY_ITEMS 获取幸运物 ↓ ForEach 渲染卡片和方块这种数据 → UI的单向流是声明式 UI 的核心思想。开发者只需要关心数据结构和渲染逻辑不需要手动操作 DOM。条件渲染的触发运势详解只在转盘停止后显示if(this.resultIdx0){// 运势详解区域Column(){// 运势卡片// 四维评分// 幸运物}.animation({duration:300})}resultIdx从 -1 变为有效值时详情区域渲染变回 -1 时详情区域销毁。动画的配合详情区域加了整体渐入动画时长 300ms。动画加在容器上而不是每个子组件上让整个区域作为一个整体渐入避免四个卡片同时动画导致的视觉混乱。踩坑记录坑 1星级的边界处理星级评分可能出现 0 星或 5 星以上的情况。代码中需要确保星级在 0 到 5 之间constclampedStarsMath.max(0,Math.min(5,item.stars));如果不做边界处理0 星会显示 5 个空星看起来像没评分6 星会导致数组越界。坑 2卡片高度不一致如果四个卡片的解读文本长度差异很大卡片高度会不一致视觉上参差不齐。解决方案有两种固定最小高度给卡片设minHeight(80)确保最矮的卡片也有一定高度截断文本给解读文本设maxLines(2)和textOverflow(TextOverflow.Ellipsis)超过两行显示省略号当前版本没有做这两个处理因为运势解读文本长度差异不大。坑 3幸运物的换行在窄屏设备上比如宽度 320px四个方块可能会换行。解决方案是用Scroll组件包裹Scroll(){Row(){ForEach(LUCKY_ITEMS,(item:LuckyItem){// 方块})}}.scrollable(ScrollDirection.Horizontal)但当前版本的方块宽度22%在大多数设备上都能一行显示不加滚动也能接受。坑 4星级的半星渲染emoji 的半星☆和满星★在视觉上差异不大用户可能分不清。如果要更精确的半星效果可以用 Canvas 组件绘制Canvas(context){// 绘制满星部分// 绘制空星部分}.clipContent(true)但实现复杂度会大幅增加对于运势应用来说不值得。坑 5ForEach 的 key 管理ForEach遍历四维数据时需要确保每个元素有唯一的 key。如果数据中有重复项比如两个维度名称相同可能会导致渲染错误。解决方案是用索引作为 keyForEach(fortuneDetails,(item:FortuneDetail,i:number){// 卡片},(item:FortuneDetail,i:number)fortune_${i})代码改进建议1. 星级组件化星级评分的渲染逻辑可以提取成独立组件Componentstruct StarRating{Propstars:number0;build(){Row(){ForEach([1,2,3,4,5],(n:number){Text(nthis.stars?★:☆).fontSize(11).fontColor(nthis.stars?#FFD700:#3B2F78)})}}}复用时只需要StarRating stars{item.stars} /代码更简洁。2. 数据持久化运势结果可以存到本地让用户可以回顾历史运势。可以用 Preferences APIimportdataPreferencesfromohos.data.preferences;// 保存awaitpreferences.put(lastFortune,JSON.stringify(fortuneData));// 读取constlastFortuneawaitpreferences.get(lastFortune,);3. 自定义星级样式如果要更精致的星级效果可以用自定义组件绘制满星、半星、空星。可以用Canvas或者StacklinearGradient实现半星效果。4. 响应式布局幸运物区域在大屏设备上可以改成两行两列布局Grid(){ForEach(LUCKY_ITEMS,(item:LuckyItem){GridItem(){// 方块}})}.columnsTemplate(1fr 1fr).rowsTemplate(1fr 1fr)5. 运势卡片的点击交互当前运势卡片是静态的。如果要更丰富的交互可以点击卡片展开更多解读.onClick((){this.expandedCategorythis.expandedCategoryitem.category?:item.category;})总结转盘运势详解的核心是数据驱动展示——把结构化数据用星级评分、卡片式布局、方块网格可视化。四维评分让用户快速了解运势重点幸运物提供实用参考。星级评分的关键是条件渲染——根据分数决定显示满星、半星还是空星。卡片式布局的关键是弹性排列——让内容自动撑开高度保持视觉整齐。幸运物的关键是等宽排列——让四个方块看起来像一组。这个模式适用于任何需要评分 解读的场景餐厅评价、电影评分、商品评价。核心思路一样只是数据内容不同。适用边界这个部分适合用作 ArkUI 数据驱动展示的学习案例涵盖了星级评分、卡片布局、弹性排列、条件渲染、动画配合等核心知识点。但如果要上架应用商店还需要补充组件化、数据持久化、自定义星级、响应式布局、点击交互等内容。建议在此基础上逐步扩展而不是一次性做完所有功能。