一、组件分类:普通组件 vs 容器组件

类别定义典型成员关键限制
普通组件(叶子)只干自己的事,不能挂子节点UIButton、UILabel、UIImageView、UICanvas、UIBoxProgress、UICircleProgress、UIEditText、UICheckbox继承自 UIView,Add() 无意义
容器组件持有子节点列表,负责排布 + 转发事件 + 转发绘制UIViewGroup、UIScrollView、UIList、UISwipeView、UIDialog、UIPicker继承自 UIViewGroup,遍历顺序决定 Z 序和事件优先级
UIView(所有组件的根)
├── 坐标/尺寸/可见性/点击监听/绘制入口/标脏(Invalidata)
│
├── UIButton ──┬── UILabelButton(文字按钮)
│              └── UIRepeatButton(长按连发)
├── UILabel
├── UIImageView ──┬── UIImageAnimatorView(帧动画)
│                 └── UITextureMapper(仿射变换贴图)
├── UICanvas(自绘留白区)
├── UIAbstractProgress ──┬── UIBoxProgress(矩形进度条)
│                        └── UICircleProgress(环形进度)
├── UIArcLabel(弧形旋转文字)
└── UIViewGroup(容器基类)
     ├── UIScrollView(滚动)──┬── UIList(列表,带复用)
     │                        └── UISwipeView(分页滑动)
     ├── UIDialog(弹窗)
     ├── UIPicker(选择器)
     └── RootView(根视图,渲染调度单例)

二、基础控件组

2.1 UILabel:最简单的叶子

  • 负责文本测量与绘制(走 font 模块,受字库裁剪影响极大)。
  • 坑:小内存设备字库常被裁剪,缺字会显示成方块,不是 bug 是配置问题;长文本不换行会越界,需手动截断或自己算行高。

2.2 UIButton / UILabelButton / UIRepeatButton

类定位关键接口
UIButton图片态按钮,支持多状态样式SetImageSrc(default, triggered)、SetStyle(ButtonState)、Enable()/Disable()
UILabelButton文字按钮(UIButton + UILabel 组合语义)设置文本、按下态颜色
UIRepeatButton长按连续触发(如加减计数器、长按快进)内部持定时器,按住周期发事件

实现要点:按钮的本质是 状态机(NORMAL / PRESSED / DISABLED)+ 命中判定 + 抬起时触发。写自定义可交互控件就照这个模板抄。

2.3 UIImageView:图片叶子

  • 依赖 imgdecode 解码,格式支持 PNG/JPEG/ARGB8888/RGB565 等(编译期宏开关控制)。
  • 两个高频坑:
    • JPEG 大图在低内存设备上解码即 OOM → 必须做缩图解码或预生成小图;
    • PNG 含 alpha 的图反复绘制 → 考虑缓存成离屏 bitmap(如果后端支持)。

三、滚动容器组:UIScrollView / UIList / UISwipeView

这是整个模块最复杂、最容易写出性能事故的一组。

3.1 UIScrollView:滚动的最小闭环

职责:维护一个可拖动的内容区,比可视区大,通过偏移量决定画哪一段。

contentSize > viewportSize  →  可滚动
drag 阶段:记录起始点 + 累计 delta → SetScrollOffset → Invalidate()
fling 阶段:初速度 → 衰减曲线 → 逐帧 tick(走 Animator)→ 停
边界:到达边缘回弹/阻尼

必查清单:

  • ✅ 是否设置了正确的 content size(设错 = 滚不动或滚出空白)
  • ✅ 拖动时是否只标脏新旧两个区域(很多实现图省事全屏 Invalidate)
  • ✅ fling 动画是否在不可见时停掉(页面隐藏要主动 stop)
  • ✅ 子控件超出是否被 clip(否则滚动时残影留在屏幕上)

3.2 UIList:列表 + 回收复用

UIList 相比 UIScrollView 多的唯一一件事就是回收复用——这也是它存在的理由。

数据项 N 个,但屏幕上只显示 K 个(K << N)
→ 只创建 K+缓冲 个 item 实例
→ 滑动时,移出屏幕的 item 被「回收」,填入新数据,挪到另一端

开发纪律:

  1. item 必须尺寸固定或可预计算(轻量系统没有复杂的自适应测量)。
  2. 复用时必须重置所有状态(选中态、颜色、文本),否则旧状态会泄露到新数据上——这是列表「错乱」的第一大原因。
  3. 不要在 item 里持有外部对象的裸指针,回收后悬空。

3.3 UISwipeView:分页滑动(轮播/Tab 页)

官方接口(南向开发常用):

表格

方法作用
UISwipeView(HORIZONTAL / VERTICAL)构造指定方向
Add(view)按添加顺序自动水平/垂直排列
SetCurrentPage(index) / GetCurrentPage()切页
GetCurrentView() / GetViewByIndex(i)取页
SetOnSwipeListener(cb)翻页回调
SetAnimatorTime(ms)翻页动画时长
SetLoopState(bool)是否循环

特性:滑动结束后当前页居中对齐(snap to page)。
坑:开启 loop 时首尾衔接的实现质量参差不齐,部分版本会出现首尾空白页,需实测;页内如果是 UIList,嵌套滚动冲突要自己在 listener 里做方向裁决。


四、输入控件:UIEditText / UICheckbox

  • UIEditText:文本输入,涉及焦点管理、软键盘/物理键盘输入、光标闪烁(逐帧动画)、文本溢出滚动。焦点是全局唯一的,切焦点时要记得把旧编辑框的键盘收起、光标停掉。
  • UICheckbox:选中态切换 + 图标切换。典型「状态驱动绘制」的最小教学案例:checked_ 变 → Invalidate() → Draw 里根据状态选图标。

共同点:它们是有「内部状态」的控件,状态变了必须标脏。记住这个模式,你自己写的控件也一样。


五、图表与画布:UICanvas / UIChart

5.1 UICanvas:自绘的「白盒」

class MyCanvas : public UICanvas {
    void Draw(const Rect& area) override {
        DrawLine(...); DrawRect(...); DrawText(...);   // 走 draw 原语
    }
};

适用:仪表盘、波形、自定义进度、签名板。
不适用:复杂交互控件(那就该继承 UIView/UIViewGroup 自己做完整控件)。

5.2 UIChart 族:折线 / 柱状 / 坐标轴

UIChart(图表容器,UIViewGroup)
├── UIAxis / UIXAxis / UIYAxis   ← 坐标轴、刻度、标签
├── UIChartPolyline              ← 折线图
└── UIChartPillar                ← 柱状图

现实提醒:这组组件能力非常基础(少量序列、无缩放交互、无大数据量优化)。数据点多起来必卡,因为每帧重绘全部图元。真要做大点数实时曲线:自己写环形 buffer + 只重绘新增段 + 降采样。


六、进度指示:UIAbstractProgress → UIBoxProgress / UICircleProgress

UIAbstractProgress(抽象进度,存 value/min/max/direction)
├── UIBoxProgress      矩形条(横/竖)
└── UICircleProgress   环形进度(圆弧绘制)
UIArcLabel             沿弧排列的文字(表盘刻度文字常用)

共性坑:进度从 30% → 31% 时,只标脏变化那一段弧/那一段矩形,不要整控件重绘。动画型进度(平滑过渡)要走 animator,并且页面退出时必须停掉,否则后台空转耗电。


七、弹窗与选择器:UIDialog / UIPicker

  • UIDialog:本质是一个覆盖在根视图上的半透明遮罩 + 居中容器,带模态事件拦截(背景点击关闭 / 拦截下方事件)。实现要点:弹出时要把下方事件拦截住,否则弹窗下面的按钮还能点——这是最常见的弹窗 bug。
  • UIPicker:多列滚动选择器,内部通常是多个 UISwipeView/UIList 组合 + 中间高亮条。列之间联动(省市区三级联动)时,注意别在回调里同步刷新另一列导致递归刷新。

八、变换与帧动画:UITextureMapper / UIImageAnimatorView

类能力说明
UIImageAnimatorView序列帧播放(从 UIImageView 继承)依赖 animator 模块逐帧切换 src;帧数多、图大 → 内存爆炸,建议预合成或使用小尺寸序列帧
UITextureMapper图片的仿射变换(旋转/缩放/平移贴图)用于表盘指针、旋钮、仪表盘指针这类「绕点旋转」的场景

表盘开发的经典组合:UITextureMapper(指针旋转)+ UIArcLabel(刻度数字)+ UICircleProgress(进度环)。这三者同屏且都在逐帧动 → 务必确认它们的脏区都是局部小矩形,否则一帧下来全屏重绘,低端芯片直接 10fps。


九、边界:什么不该在 components 里做

表格

想做正确归属
算布局、自适应排布layout/(FlexLayout / GridLayout / ListLayout)
逐帧属性动画animator/(AnimatorManager)
画一条线/一个圆/一段文字draw/ 原语,不要自己在组件里操作 pixel buffer
响应触摸/按键events/,组件只接回调
创建窗口、上屏合成window/ + window_manager_lite
跨进程事件、状态同步上层应用框架,不是 ui_lite
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐