从原生组件到自定义组件

掌握了 Column、Row、List 这些基础组件后,下一步是把重复出现的界面块抽成自己的组件。自定义组件有两个价值:一是复用,二是把样式与状态封装起来,让页面主体只关心业务。ArkUI 的声明式写法天然利于组件化——一个 struct 就是一块界面。

进阶的关键不是会用多少内置组件,而是能把一屏界面拆成职责清晰、可独立复用的若干自定义组件。

这篇从组件封装讲到动画与转场,目标是让界面既好看又顺滑。

@Builder 与组件封装

用 struct 声明一个自定义组件,外部通过参数传入内容,内部用 build 方法描述布局。简单的局部复用可以直接用 @Builder 装饰的函数,避免为一小段布局专门建 struct。

@Component
struct SectionCard {
  @Prop title: string
  @Builder content() {
    // 由调用方传入
  }
  build() {
    Column({ space: 12 }) {
      Text(this.title).fontSize(18).fontWeight(FontWeight.Bold)
      this.content()
    }.padding(16).backgroundColor(Color.White).borderRadius(16)
  }
}

这样 SectionCard 就成了一个带标题的卡片容器,任何页面都能复用它的圆角、留白与阴影,只换标题和内容。

状态在组件间的传递

自定义组件好不好用,取决于状态传递是否清晰。ArkUI 提供一组装饰器来描述数据流的方向:@Prop 是父到子的单向只读,@Link 是父子双向绑定,@State 是组件内部自己的状态,@Provide/@Consume 做跨层级共享。

状态装饰器速查
  • @State组件内部状态,变了就刷新。
  • @Prop父传子、单向、只读副本。
  • @Link父子双向同步同一份状态。
  • @Provide/@Consume跨多层级共享,避免层层透传。

原则上:能单向就别双向,能内部就别外泄。状态边界清晰,界面才好维护。

属性动画与显式动画

ArkUI 的动画分两种思路。属性动画给属性加 animateTo,状态一变,属性自动从旧值平滑过渡到新值;显式动画用 animateTo 包一段状态修改,指定曲线与时长。

Button("展开")
  .onClick(() => {
    animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
      this.expanded = !this.expanded
    })
  })

Text("内容").height(this.expanded ? 120 : 0).opacity(this.expanded ? 1 : 0)

改一个布尔值,高度和透明度就自动动起来了。曲线选 EaseOut 更贴近人对"快速到位再缓停"的直觉。

页面转场与共享元素

页面跳转默认是整页淡入,略显生硬。用 transition 给进入退出加缩放与位移,特别是让两张页面里的同一张卡片做共享元素转场——点列表卡片,它放大铺成详情页,视觉上连贯很多。

鸿蒙 UI 进阶示意:三块抽象窗口卡片之间由蓝色流动光带连接,表现组件流转与转场动画
转场动画:卡片之间用连续的动效衔接,界面切换不再是生硬的跳变。

核心是给共享元素设同一个 transitionId,系统会自动在两个页面间补间。别给所有元素都加转场,挑关键的一两个就够。

手势联动动画

把手势和动画接起来,界面就有了"跟手"的质感。用 PanGesture 拿到偏移量,实时驱动组件的平移与透明度;抬手时用 animateTo 让它回弹到目标位置。下拉刷新、侧滑删除都是这套模式。

  • 手势进行中:直接改状态,跟手渲染。
  • 手势结束:按速度与位置判断,动画归位。
  • 注意不要在手势回调里做重计算,保持每帧轻量。