1 简介
本文基于OpenHarmony 3.0为基础,讲解Graphic子系统的UI。 图形UI组件实现了一套系统级的图形引擎,该组件为应用开发提供UIKit接口,包括了动画、布局、图形转换、事件处理,以及丰富的UI组件。组件内部直接调用HAL接口,或者使用WMS(Window Manager Service)提供的客户端与硬件交互,以完成事件响应、图像绘制等操作。目前只看到在L1有使用。
1.1 图形子系统相关
- 《OpenHarmony 源码解析之图形子系统 (一)》
- 《OpenHarmony 源码解析之图形子系统(UI)》
1.2 OpenHarmony 架构图

1.3 图形子系统架构图

2 基础知识
2.1 代码目录
/foundation/graphic/ui
├── frameworks # 框架代码
│ ├── animator # 动画模块
│ ├── common # 公共模块
│ ├── components # 组件
│ ├── core # ui主流程(渲染、任务管理等)
│ ├── default_resource
│ ├── dfx # 维测功能
│ ├── dock # 驱动适配层
│ │ └── ohos # ohos平台适配
│ ├── draw # 绘制逻辑
│ ├── engines # 绘制引擎
│ │ ├── dfb
│ │ ├── general
│ │ ├── gpu_vglite
│ │ └── software_zlite
│ ├── events # 事件
│ ├── font # 字体
│ ├── imgdecode # 图片管理
│ ├── layout # 页面布局
│ ├── themes # 主题管理
│ ├── window # 窗口管理适配层
│ └── window_manager
│ └── dfb
├── interfaces # 接口
│ ├── innerkits # 模块间接口
│ │ └── xxx # 子模块的接口
│ └── kits # 对外接口
│ └── xxx # 子模块的接口
├── test # 测试代码
│ ├── framework
│ │ ├── include # 测试框架头文件
│ │ └── src # 测试框架源码
│ ├── uitest # 显示效果测试(可执行程序在foundation/graphic/wms/test:sample_ui)
│ │ └── test_xxx # 具体UI组件效果测试
│ └── unittest # 单元测试
│ └── xxx # 具体UI组件单元测试
└── tools # 测试和模拟器工具(模拟器工程、资源文件)
└── qt # QT工程
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
2.2 图形组件一览

3 实践
3.1 UI控件效果
具体UI控件效果可以通过QT Creator运行QT工程,效果如下:


所有UI控件在工程都可以找到效果,通过查看工程代码可以了解到各控件的使用方式以及参数详情。
3.2 示例
下面我们举例UIButton解析控件的实现:
构造函数-参数
UIButton::UIButton()
: defaultImgSrc_(nullptr),
triggeredImgSrc_(nullptr),
currentImgSrc_(ButtonImageSrc::BTN_IMAGE_DEFAULT),
imgX_(0),
imgY_(0),
contentWidth_(0),
contentHeight_(0),
state_(RELEASED),
styleState_(RELEASED),
#if DEFAULT_ANIMATION
enableAnimation_(true),
animator_(*this),
#endif
buttonStyleAllocFlag_(false)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
设置Theme
void UIButton::SetupThemeStyles()
{
Theme* theme = ThemeManager::GetInstance().GetCurrent();
if (theme == nullptr) {
buttonStyles_[RELEASED] = &(StyleDefault::GetButtonReleasedStyle());
buttonStyles_[PRESSED] = &(StyleDefault::GetButtonPressedStyle());
buttonStyles_[INACTIVE] = &(StyleDefault::GetButtonInactiveStyle());
} else {
buttonStyles_[RELEASED] = &(theme->GetButtonStyle().released);
buttonStyles_[PRESSED] = &(theme->GetButtonStyle().pressed);
buttonStyles_[INACTIVE] = &(theme->GetButtonStyle().inactive);
}
style_ = buttonStyles_[RELEASED];
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
绘制OnDraw
void UIButton::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
{
OpacityType opa = GetMixOpaScale();
BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, GetOrigRect(), invalidatedArea, *buttonStyles_[state_], opa);
DrawImg(gfxDstBuffer, invalidatedArea, opa);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
可以看到需要绘制2个,第一个通过绘制引擎绘制点->DrawRect,
void BaseGfxEngine::DrawRect(BufferInfo& dst,
const Rect& rect,
const Rect& dirtyRect,
const Style& style,
OpacityType opacity)
{
DrawRect::Draw(dst, rect, dirtyRect, style, opacity);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
第2个绘制图片->DrawImg
void UIButton::DrawImg(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, OpacityType opaScale)
{
const Image* image = GetCurImageSrc();
if (image == nullptr) {
return;
}
ImageHeader header = {0};
image->GetHeader(header);
Rect coords;
Rect viewRect = GetContentRect();
coords.SetLeft(viewRect.GetLeft() + GetImageX());
coords.SetTop(viewRect.GetTop() + GetImageY());
coords.SetWidth(header.width);
coords.SetHeight(header.height);
Rect trunc(invalidatedArea);
if (trunc.Intersect(trunc, viewRect)) {
image->DrawImage(gfxDstBuffer, coords, trunc, *buttonStyles_[state_], opaScale);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
可以发现最终还是调用draw目录下通过绘制点、线、图片等等来进行绘制:

事件处理
UIButton只重写了OnPressEvent,OnReleaseEvent和OnCancelEvent,增加了动画,具体实现还是在基类UIView,主要使用的函数:
void UIView::InvalidateRect(const Rect& invalidatedArea)
{
if (!visible_) {
if (needRedraw_) {
needRedraw_ = false;
} else {
return;
}
}
Rect trunc(invalidatedArea);
bool isIntersect = true;
UIView* par = parent_;
UIView* cur = this;
while (par != nullptr) {
if (!par->visible_) {
return;
}
isIntersect = trunc.Intersect(par->GetContentRect(), trunc);
if (!isIntersect) {
break;
}
cur = par;
par = par->parent_;
}
if (isIntersect && (cur->GetViewType() == UI_ROOT_VIEW)) {
RootView* rootView = reinterpret_cast<RootView*>(cur);
rootView->AddInvalidateRectWithLock(trunc, this);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
在UIView里面可以发现还有很多事件比如:OnLongPressEvent,我们可以重写来自定义效果。
UIView重要函数说明
(1) OnPreDraw——准备绘制
(2) OnDraw——绘制动作
(3) OnPostDraw——能在UI线程绘制
(4) Invalidate——请求重新绘制,有需要更新界面就可以调用此函数重新绘制
(5) Scale——缩放事件
(6) Translate——移动事件
(7) OnPressEvent等——触摸事件
还有很多常用的函数,有兴趣的同学可以自行查阅。
4 总结
到这我们对UI控件的一个使用和效果都有了解,底层实现流程也熟悉。不管是直接绘制还是通过绘制引擎绘制,最终还是调用draw目录下的绘制函数。自定义控件我们可以继承现有控件,扩展实现效果,还可以直接继承基类UIView。