• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用主题字体(ArkTS)
2<!--Kit: ArkGraphics 2D-->
3<!--Subsystem: Graphics-->
4<!--Owner: @oh_wangxk; @gmiao522; @Lem0nC-->
5<!--Designer: @liumingxiang-->
6<!--Tester: @yhl0101-->
7<!--Adviser: @ge-yafang-->
8## 场景介绍
9
10主题字体,特指系统**主题应用**中能使用的字体,属于一种特殊的自定义字体,可以通过相关接口调用使能主题应用中的主题字体。
11
12## 实现机制
13
14**图1** 主题字体的切换和使用
15
16![themeText_native](figures/themeText_native.jpg)
17
18针对主题字的切换使用,应用方应确保订阅主题字变更事件,当接收字体变更事件后,由应用方主动调用页面刷新才能实现主题字的切换,否则主题字只能在重启应用后才生效。
19
20
21## 接口说明
22
23注册使用主题字体的常用接口如下表所示,详细接口说明请见[@ohos.graphics.text (文本模块)](../reference/apis-arkgraphics2d/js-apis-graphics-text.md)。
24
25| 接口 | 描述 |
26| -------- | -------- |
27| getGlobalInstance(): FontCollection | 获取应用全局字体集的实例。 |
28
29
30## 开发步骤
31
321. 请确保在设备系统**主题应用**中,能成功应用一项主题字体。
33
342. 导入依赖的相关模块。
35
36   ```ts
37   import { text } from '@kit.ArkGraphics2D';
38   ```
39
403. 使用getGlobalInstance()接口获取全局字体集对象,系统框架在注册主题字体过程中仅会将主题字体信息传入全局字体集对象中。
41
42   ```ts
43   let fontCollection = text.FontCollection.getGlobalInstance();
44   ```
45
464. 创建段落样式,并使用字体管理器实例构造段落生成器ParagraphBuilder实例,用于生成段落。
47   > **说明:**
48   >
49   > 在生成段落对象设置段落样式入参时,不能指定fontFamilies属性,否则会变为优先使用指定字体而非主题字体。
50   >
51   > 若未在系统**主题应用**中设置一项主题字体,则将使用系统默认字体进行绘制。
52
53   ```ts
54   // 设置文本样式
55   let myTextStyle: text.TextStyle = {
56       color: { alpha: 255, red: 255, green: 0, blue: 0 },
57       fontSize: 100,
58       // fontFamilies:['Test Font'] // 不要指定fontFamilies,否则优先使用指定字体
59   };
60   // 创建一个段落样式对象,以设置排版风格
61   let myParagraphStyle: text.ParagraphStyle = {textStyle: myTextStyle}
62   // 创建一个段落生成器
63   let paragraphBuilder: text.ParagraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);
64   ```
65
665. 设置文本样式,添加文本内容,并生成段落文本用于后续文本的绘制显示。
67
68   ```ts
69   // 在段落生成器中设置文本样式
70   paragraphBuilder.pushStyle(myTextStyle);
71   // 在段落生成器中设置文本内容
72   paragraphBuilder.addText("Hello World. \nThis is the theme font.");
73   // 通过段落生成器生成段落
74   let paragraph = paragraphBuilder.build();
75   ```
76
776. 创建渲染节点,并保存到数组。(此处示例代码为简化逻辑,采用数组作为容器,实际开发中应结合应用情况选择更恰当的容器来保证节点的添加与删除对应。)
78
79   ```ts
80   // 创建渲染节点数组
81   const renderNodeMap: Array<RenderNode> = new Array();
82   // 创建节点控制器
83   class MyNodeController extends NodeController {
84     private rootNode: FrameNode | null = null;
85     makeNode(uiContext: UIContext): FrameNode {
86       this.rootNode = new FrameNode(uiContext)
87       if (this.rootNode == null) {
88         return this.rootNode
89       }
90       const renderNode = this.rootNode.getRenderNode()
91       if (renderNode != null) {
92         renderNode.frame = { x: 0, y: 0, width: 300, height: 50 }
93         renderNode.pivot = { x: 0, y: 0 }
94       }
95       return this.rootNode
96     }
97     addNode(node: RenderNode): void {
98       if (this.rootNode == null) {
99         return
100       }
101       const renderNode = this.rootNode.getRenderNode()
102       if (renderNode != null) {
103         renderNode.appendChild(node)
104         // 将节点添加到渲染节点数组中
105         renderNodeMap.push(node)
106       }
107     }
108     clearNodes(): void {
109       if (this.rootNode == null) {
110         return
111       }
112       const renderNode = this.rootNode.getRenderNode()
113       if (renderNode != null) {
114         renderNode.clearChildren()
115         // 将节点从渲染节点数组中移除
116         renderNodeMap.pop()
117       }
118     }
119   }
120   let paragraph = paragraphBuilder.build();
121   ```
122
1237. 创建渲染节点更新函数,并导出函数,供其他文件(如:EntryAbility.ets)使用;重绘制节点目的为更新排版中字体信息,若不更新字体信息,使用之前残留结果,可能造成文字乱码。
124
125   ```ts
126   // 导出渲染节点更新函数
127   export function updateRenderNodeData() {
128     renderNodeMap.forEach((node) => {
129       // 主动触发节点重绘制
130       node.invalidate()
131     })
132   }
133   ```
134
1358. 在EntryAbility.ets中接收主题字变更事件,并调用渲染节点更新函数。
136
137   ```ts
138   // entry/src/main/ets/entryability/EntryAbility.ets
139   export default class EntryAbility extends UIAbility {
140       // ...
141       preFontId ="";
142       onConfigurationUpdate(newConfig: Configuration):void{
143           let fontId = newConfig.fontId;
144           if(fontId && fontId !=this.preFontId){
145               this.preFontId = fontId;
146               updateRenderNodeData();
147           }
148       }
149       // ...
150   }
151   ```
152
153
154## 完整示例
155
156这里以使用主题字体绘制"Hello World. \nThis is the theme font."文本为例,提供完整的示例和效果示意图。
157
158```ts
159// /pages/Index.ets
160import { NodeController, FrameNode, RenderNode, DrawContext } from '@kit.ArkUI';
161import { UIContext } from '@kit.ArkUI';
162import { text } from '@kit.ArkGraphics2D';
163
164// 创建一个自定义的渲染节点类,用于绘制文本
165class MyRenderNode extends RenderNode {
166  async draw(context: DrawContext) {
167    // 获取画布canvas对象
168    const canvas = context.canvas;
169    // 设置文本样式
170    let myTextStyle: text.TextStyle = {
171      color: { alpha: 255, red: 255, green: 0, blue: 0 },
172      fontSize: 33
173    };
174    // 创建一个段落样式对象,以设置排版风格
175    let myParagraphStyle: text.ParagraphStyle = {
176      textStyle: myTextStyle,
177      align: 3,
178      wordBreak:text.WordBreak.NORMAL
179    };
180    // 获取字体管理器全局FontCollection实例
181    let fontCollection = text.FontCollection.getGlobalInstance(); // 获取Arkui全局FC
182    // 创建一个段落生成器
183    let paragraphGraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);
184    // 在段落生成器中设置文本样式
185    paragraphGraphBuilder.pushStyle(myTextStyle);
186    // 在段落生成器中设置文本内容
187    paragraphGraphBuilder.addText("Hello World. \nThis is the theme font.");
188    // 通过段落生成器生成段落
189    let paragraph = paragraphGraphBuilder.build();
190    // 布局
191    paragraph.layoutSync(2500);
192    paragraph.paint(canvas, 0, 400);
193  }
194}
195// 创建渲染节点数组
196const renderNodeMap: Array<RenderNode> = new Array();
197// 导出渲染节点更新函数
198export function updateRenderNodeData() {
199  renderNodeMap.forEach((node) => {
200    // 主动触发节点重绘制
201    node.invalidate();
202  });
203}
204
205class MyNodeController extends NodeController {
206  private rootNode: FrameNode | null = null;
207  makeNode(uiContext: UIContext): FrameNode {
208    this.rootNode = new FrameNode(uiContext);
209    if (this.rootNode == null) {
210      return this.rootNode;
211    }
212    const renderNode = this.rootNode.getRenderNode();
213    if (renderNode != null) {
214      renderNode.frame = { x: 0, y: 0, width: 300, height: 50 };
215      renderNode.pivot = { x: 0, y: 0 };
216    }
217    return this.rootNode;
218  }
219  addNode(node: RenderNode): void {
220    if (this.rootNode == null) {
221      return;
222    }
223    const renderNode = this.rootNode.getRenderNode();
224    if (renderNode != null) {
225      renderNode.appendChild(node);
226      // 将节点添加到渲染节点数组中
227      renderNodeMap.push(node);
228    }
229  }
230  clearNodes(): void {
231    if (this.rootNode == null) {
232      return;
233    }
234    const renderNode = this.rootNode.getRenderNode();
235    if (renderNode != null) {
236      renderNode.clearChildren();
237      // 将节点从渲染节点数组中移除
238      renderNodeMap.pop();
239    }
240  }
241}
242
243// 创建一个TextRenderNode对象
244const textNode = new MyRenderNode();
245// 定义textNode的像素格式
246textNode.frame = {
247  x: 0,
248  y: 100,
249  width: 600,
250  height: 800
251};
252textNode.pivot = { x: 0.2, y: 0.8 };
253textNode.scale = { x: 1, y: 1 };
254
255@Entry
256@Component
257struct RenderTest {
258  private myNodeController: MyNodeController = new MyNodeController();
259  build() {
260    Column() {
261      Row() {
262        NodeContainer(this.myNodeController)
263          .height('100%')
264      }
265      .height('90%')
266      .backgroundColor(Color.White)
267      Row(){
268        Button("Draw Text")
269          .fontSize('16fp')
270          .fontWeight(500)
271          .margin({ bottom: 24, right: 12 })
272          .onClick(() => {
273            this.myNodeController.clearNodes();
274            this.myNodeController.addNode(textNode);
275          })
276          .width('50%')
277          .height(40)
278          .shadow(ShadowStyle.OUTER_DEFAULT_LG)
279      }
280      .width('100%')
281      .justifyContent(FlexAlign.Center) // 设置当前Row容器内子元素在主轴上居中对齐
282      .shadow(ShadowStyle.OUTER_DEFAULT_SM) // 设置Row容器外阴影效果
283      .alignItems(VerticalAlign.Bottom) // 设置当前Row容器内子元素在交叉轴(垂直方向)上的对齐方式为底部对齐
284      .layoutWeight(1) // 设置当前Row在父容器Column中的布局权重为1
285    }
286  }
287}
288
289
290```
291
292```ts
293// entry/src/main/ets/entryability/EntryAbility.ets
294import { AbilityConstant, Configuration, UIAbility, Want } from '@kit.AbilityKit';
295import { hilog } from '@kit.PerformanceAnalysisKit';
296import { window } from '@kit.ArkUI';
297import { updateRenderNodeData } from '../pages/Index';
298export default class EntryAbility extends UIAbility {
299    // ...
300    preFontId ="";
301    onConfigurationUpdate(newConfig: Configuration):void{
302        let fontId = newConfig.fontId;
303        if(fontId && fontId !=this.preFontId){
304            this.preFontId = fontId;
305            updateRenderNodeData();
306        }
307    }
308    // ...
309}
310```
311
312## 效果展示
313
314以下展示了在系统**主题应用**中切换使用不同主题字体后,对应的文字渲染效果。
315
316不同主题字体显示效果不同,此处仅示意。
317
318**图2** 主题字体1的效果 
319
320![themeFont_ts_01](figures/themeFont.PNG)
321
322**图3** 主题字体2的效果
323
324![themeFont_ts_02](figures/themeFont_ts_02.png)
325