前言
鸿蒙 Text: 扶我起来!
对于文本来说,文本的溢出效果,即超出之后显示 ...
,在业务当中是经常碰到的。下面我们看看 原生
以及 web
端的支持情况。(如有不对,望指正。)
- 溢出效果的自定义,即希望溢出的效果不是单调的
...
,而且可以指定任何效果。
平台 | ellipsis 自定义 |
---|---|
android | 不支持 |
Ios | 不支持 |
web | 不支持 |
flutter | 不支持(ExtendedText 支持) |
鸿蒙 Next | ellipsis |
- 溢出效果的位置,即在开头,中间,还是结尾。
平台 | 开头 | 中间 | 结尾 |
---|---|---|---|
android | android:ellipsize = "start" | android:ellipsize = "middle" | android:ellipsize = "end" |
Ios | NSLineBreakByTruncatingHead | NSLineBreakByTruncatingMiddle | NSLineBreakByTruncatingTail |
web | text-overflow: ellipsis clip | 不支持 | text-overflow: clip ellipsis |
flutter | 不支持(ExtendedText 支持) | 不支持(ExtendedText 支持) | TextOverflow.ellipsis |
鸿蒙 Next | EllipsisMode.START | EllipsisMode.MIDDLE | EllipsisMode.END |
光从支持情况来看,鸿蒙 Next
已经吊打其他平台了。 但是,支持也是有局限的。
-
ellipsis
只支持字符串修改,不是任意的组件。 -
EllipsisMode.START
和EllipsisMode.MIDDLE
仅在单行超长文本生效。
Flutter Text: 扶我起来 3年前,在 Flutter
上面基于 Canvas
实现了溢出效果的自定义和溢出效果位置的设置。这一次我把它们带到了鸿蒙平台。
安装
ohpm install @candies/extended_text
使用
完整例子: github.com/HarmonyCand…
参数
参数 | 类型 | 描述 |
---|---|---|
text | string | 字符串内容 |
textSpan | InlineSpan | 用于创建特殊文本的基类 |
joinZeroWidthSpace | boolean | 是否添加零宽度的空白,对应英文等换行更加紧凑 |
paragraphStyle | text.ParagraphStyle (import { text } from "@kit.ArkGraphics2D") | 文本的样式 |
overflowWidget | TextOverflowWidget | 用于定义溢出效果 |
specialTextSpanBuilder | SpecialTextSpanBuilder | 用于创建特殊文本 |
fontCollection | text.FontCollection | 自定义字体 |
import { ColorUtils, ExtendedText } from '@candies/extended_text'
import { MySpecialTextSpanBuilder } from '../text/special/MySpecialTextSpanBuilder';
@Entry
@Component
struct Index {
private specialTextSpanBuilder: MySpecialTextSpanBuilder = new MySpecialTextSpanBuilder();
context: Context = getContext(this);
content: string = MySpecialTextSpanBuilder.content;
@State joinZeroWidthSpace: boolean = false;
build() {
Column() {
ExtendedText({
text: this.content,
specialTextSpanBuilder: this.specialTextSpanBuilder,
paragraphStyle: {
textStyle: {
color: ColorUtils.resourceColorTo2DColor($r('sys.color.font'), this.context),
fontSize: vp2px(18),
},
},
joinZeroWidthSpace: this.joinZeroWidthSpace,
})
}
}
}
InlineSpan
用于创建特殊文本的基类
参数 | 类型 | 描述 |
---|---|---|
style | text.TextStyle | 文本的样式 |
actualText | string | 该特殊文本的真实文本(不一定等于显示的文本) |
start | number | 该特殊文本位于整个文本中的位置 |
export interface InlineSpanOptions {
style?: text.TextStyle;
actualText?: string;
start?: number;
}
TextSpan
继承于 InlineSpan
,用于显示纯文本。
参数 | 类型 | 描述 |
---|---|---|
text | string | 显示的文案 |
children | Array<InlineSpan> | 作为子节点,类型是 InlineSpan |
export interface TextSpanOptions extends InlineSpanOptions {
text?: string;
children?: Array<InlineSpan>;
}
PlaceholderSpan
用于占位符的 Span
参数 | 类型 | 描述 |
---|---|---|
align | text.PlaceholderAlignment | 占位符的对齐方式 |
baseline | text.TextBaseline | 占位符的基线类型 |
baselineOffset | number | 位符的基线偏移量 |
export interface PlaceholderSpanOptions extends InlineSpanOptions {
/**
* Alignment mode of placeholder.
* @type { PlaceholderAlignment }
* @syscap SystemCapability.Graphics.Drawing
* @since 12
*/
align?: text.PlaceholderAlignment;
/**
* Baseline of placeholder.
* @type { TextBaseline }
* @syscap SystemCapability.Graphics.Drawing
* @since 12
*/
baseline?: text.TextBaseline;
/**
* Baseline offset of placeholder.
* @type { number }
* @syscap SystemCapability.Graphics.Drawing
* @since 12
*/
baselineOffset?: number;
}
WidgetSpan
继承于 PlaceholderSpan
,用于显示显示组件。
参数 | 类型 | 描述 |
---|---|---|
builder | WrappedBuilder<[ESObject]> | 用于显示组件的 WrappedBuilder |
builderArgs | ESObject | 用于显示组件的 WrappedBuilder 的参数 |
hide | boolean | 是否需要显示该组件 |
export interface WidgetSpanOptions extends PlaceholderSpanOptions {
builder: WrappedBuilder<[ESObject]>;
builderArgs: ESObject;
hide?: boolean,
}
注意: buildHyperlink
只能是全局的 @Builder
@Builder
export function buildHyperlink(builderArgs: ESObject) {
Row(){
Hyperlink(builderArgs[0], builderArgs[1])
}
}
return new WidgetSpan({
builder: wrapBuilder<[ESObject]>(buildHyperlink),
builderArgs: [href, content],
style: {
fontSize: vp2px(18), color: extended_text.ColorUtils.resourceColorTo2DColor(Color.Blue),
fontWeight: text.FontWeight.W600,
},
actualText: this.toString(),
start: this.start,
});
OverflowWidget
用于自定义文本的溢出效果。
- 目前官方支持修改
ellipsis
,但是只能是字符串 - 目前官方支持
ellipsisMode
,但是只支持单行文本
参数 | 类型 | 描述 |
---|---|---|
builder | WrappedBuilder<[ESObject]> | 用于显示溢出组件的 WrappedBuilder |
builderArgs | ESObject | 用于显示溢出组件的 WrappedBuilder 的参数 |
position | TextOverflowPosition | 用于控制溢出组件的位置(start,middle,end) |
export enum TextOverflowPosition {
start,
middle,
end,
}
export interface TextOverflowWidgetOptions {
builder: WrappedBuilder<[ESObject]>;
position?: TextOverflowPosition;
builderArgs?: ESObject;
}
SpecialTextSpanBuilder
帮助将字符串文本快速转换为特殊的 InlineSpan
SpecialText
下面的例子告诉你怎么创建一个 $xxx$
具体思路是对字符串进行进栈遍历,通过判断 flag
来判定是否是一个特殊字符。
例子:$xxx$
,以 $
开头并且以 $
结束,我们就认为它是一个 $xxx$
的特殊文本
export class DollarText extends extended_text.SpecialText {
static flag: string = '$';
constructor(context: Context, start?: number, textStyle?: text.TextStyle,) {
super(DollarText.flag, DollarText.flag, context, start, textStyle);
}
finishText(): extended_text.InlineSpan {
let text = this.getContent();
return new TextSpan({
text: text,
style: {
fontSize: vp2px(18), color: extended_text.ColorUtils.resourceColorTo2DColor(Color.Orange),
},
actualText: this.toString(),
start: this.start,
});
}
}
特殊文本 Builder
创建属于你自己规则的 Builder
,上面说了你可以继承 SpecialText
来定义各种各样的特殊文本。
build
方法中,是通过具体思路是对字符串进行进栈遍历,通过判断flag
来判定是否是一个特殊文本。 感兴趣的,可以看一下SpecialTextSpanBuilder
里面build
方法的实现,当然你也可以写出属于自己的build
逻辑createSpecialText
通过判断flag
来判定是否是一个特殊文本
import * as extended_text from '@candies/extended_text'
import { text } from "@kit.ArkGraphics2D"
import { DollarText } from './DollarText';
import { EmojiText } from './EmojiText';
import { LinkText } from './LinkText';
export class MySpecialTextSpanBuilder extends extended_text.SpecialTextSpanBuilder {
createSpecialText(flag: string, index: number, context: Context,
textStyle?: text.TextStyle | undefined): extended_text.SpecialText | null {
if (this.isStart(flag, EmojiText.flag)) {
return new EmojiText(context, index - (EmojiText.flag.length - 1), textStyle,);
} else if (this.isStart(flag, DollarText.flag)) {
return new DollarText(context, index - (DollarText.flag.length - 1), textStyle);
} else if (this.isStart(flag, LinkText.flag)) {
return new LinkText(context, index - (LinkText.flag.length - 1), textStyle);
}
return null;
}
}
RegExpSpecialTextSpanBuilder
当然,也提供了通过正则的方式,创建特殊文本的方式。
RegExpSpecialText
下面的例子告诉你怎么创建一个 $xxx$
你只需要继承 RegExpSpecialText
, 并且写出来对应的正则表达式即可。
import * as extended_text from '@candies/extended_text'
import { TextSpan } from '@candies/extended_text';
import { text } from "@kit.ArkGraphics2D"
export class RegExpDollarText extends extended_text.RegExpSpecialText {
get regExp(): RegExp {
return new RegExp('\\$(.+?)\\$', 'g');
}
finishText(start: number,
match: RegExpExecArray,
context: Context,
textStyle?: text.TextStyle,): extended_text.InlineSpan {
let text = match[0];
return new TextSpan({
text: text.replaceAll('$', ''),
style: {
fontSize: vp2px(18), color: extended_text.ColorUtils.resourceColorTo2DColor(Color.Orange),
},
actualText: this.toString(),
start: start,
});
}
}
特殊文本 Builder
将上一步创建的 RegExpSpecialText
,放到 regExps
当中即可。
import { RegExpSpecialTextSpanBuilder } from '@candies/extended_text';
import { RegExpEmojiText } from './EmojiText';
import { RegExpDollarText } from './DollarText';
import { RegExpLinkText } from './LinkText';
export class MyRegExpSpecialTextSpanBuilder extends RegExpSpecialTextSpanBuilder {
get regExps() {
return [
new RegExpDollarText(),
new RegExpEmojiText(),
new RegExpLinkText(),
];
}
}
实现
ParagraphBuilder
实现的前提是对文本的计算,Flutter
平台是 TextPainter
, 鸿蒙平台对应的 api
是 ParagraphBuilder, 通过 ParagraphStyle 和 FontCollection 创建一个 ParagraphBuilder
。
let myTextStyle: text.TextStyle = {
color: { alpha: 255, red: 255, green: 0, blue: 0 },
fontSize: 33,
};
let myParagraphStyle: text.ParagraphStyle = {
textStyle: myTextStyle,
align: text.TextAlign.END,
};
let fontCollection = new text.FontCollection();
let paragraphGraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);
ParagraphStyle
ParagraphStyle
的参数如下,各个平台基本一致。
名称 | 类型 | 只读 | 可选 | 说明 |
---|---|---|---|---|
textStyle | TextStyle | 是 | 是 | 作用于整个段落的文本样式,默认为初始的TextStyle。 |
textDirection | TextDirection | 是 | 是 | 文本方向,默认为LTR。 |
align | TextAlign | 是 | 是 | 文本对齐方式,默认为START。 |
wordBreak | WordBreak | 是 | 是 | 断词类型,默认为BREAK_WORD。 |
maxLines | number | 是 | 是 | 最大行数限制,整数,默认为1e9。 |
breakStrategy | BreakStrategy | 是 | 是 | 断行策略,默认为GREEDY。 |
strutStyle | StrutStyle | 是 | 是 | 支柱样式,默认为初始的StrutStyle。 |
textHeightBehavior | TextHeightBehavior | 是 | 是 | 文本高度修饰符模式,默认为ALL。 |
FontCollection
FontCollection
可以用于获取全局字体实例或者加载自定义字体。
- 获取应用全局FontCollection的实例
static getGlobalInstance(): FontCollection
- 同步接口,将路径对应的文件,以
name
作为使用的别名,加载成自定义字体。其中参数name
对应的值需要在TextStyle中的fontFamilies
属性配置,才能显示自定义的字体效果。支持的字体文件格式包含:ttf
、otf
。
loadFontSync(name: string, path: string | Resource): void
- 清理字体排版缓存(字体排版缓存本身设有内存上限和清理机制,所占内存有限,如无内存要求,不建议清理)。
clearCaches(): void
插入文字
用于向正在构建的文本段落中插入具体的文本字符串。
addText(text: string): void
插入占位符
用于在构建文本段落时插入占位符,这是我们用来插入 WidgtSpan
对应的组件。
addPlaceholder(placeholderSpan: PlaceholderSpan): void
插入具体的符号
用于向正在构建的文本段落中插入具体的符号。
addSymbol(symbolId: number): void
更新文本的样式
pushStyle(textStyle: TextStyle): void
popStyle(): void
更新当前文本块的样式 ,直到对应的 popStyle 操作被执行,会还原到上一个文本样式。
就是说如果我们某个时刻 push
了一个新的 style
,那么之后的样式都将应用到 addText
, addPlaceholder
, addSymbol
方法,直到我们调用 popStyle
。
完成段落的构建过程
build(): Paragraph
用于完成段落的构建过程,生成一个可用于后续排版渲染的段落对象。
Paragraph
通过 ParagraphGraphBuilder
,我们创建了对应的 Paragraph。
let paragraph = paragraphGraphBuilder.build();
这些 api
都很熟悉,各个平台基本一致。
layoutSync
layoutSync
传入宽度,之后我们就获取文本的各种数据。
计算溢出
didExceedMaxLines
和 getHeight
2
个方法组合,就可以判断该文本是否溢出。
- 如果容器的高度小于文本高度,或者文本超出了限制的行数,属于溢出。
- 如果容器的宽度小于文本宽度,属于溢出。
_didVisualOverflow(paragraph: text.Paragraph, constraint: ConstraintSizeOptions): boolean {
let textSize: SizeResult = {
width: px2vp(paragraph.getMaxWidth()),
height: px2vp(paragraph.getHeight()),
};
let size: SizeResult = {
width: constraint.maxWidth! as number,
height: constraint.maxHeight! as number,
}
let textDidExceedMaxLines =
paragraph.didExceedMaxLines();
let
didOverflowHeight =
size.height < textSize.height || textDidExceedMaxLines;
let
didOverflowWidth = size.width < textSize.width;
let hasVisualOverflow = didOverflowWidth || didOverflowHeight;
return hasVisualOverflow;
}
绘制到画布
在画布上以坐标点 (x, y) 为左上角位置绘制文本。
paint(canvas: drawing.Canvas, x: number, y: number): void
布局
上面我们讲了在鸿蒙上面我们怎么创建文本,计算文本,绘制文本。接下来,我们需要处理的是,我们怎么获取到容器的宽高,WidgetSpan
的大小获取,以及布局。我们整个布局时由一个 NodeContainer
和 ExtendedParagraph
组成,一个负责绘制文本,一个负责计算 OverflowWidget
和文本中的 WidgetSpan
的位置以及放置它们。
OverflowWidget
和文本中的 WidgetSpan
,都在 ExtendedParagraph
中布局计算绘制,而 NodeContainer
绘制文本的过程中,我们需要将 OverflowWidget
组件所在区域的文本裁剪掉。
build(){
Stack(){
NodeContainer(this.myNodeController)
ExtendedParagraph()
}
}
NodeContainer
build(){
Stack(){
NodeContainer(this.textNodeController)
}
}
NodeContainer 用于绘制文本。
主要的过程是: 当需要绘制的时候,将计算好的 Paragraph
和 overflowClipRects
带入 TextRenderNode
,然后添加到 TextNodeController
中。overflowClipRects
即为需要裁剪的文本区域,这个区域放置了 OverflowWidget
。
class TextRenderNode extends RenderNode {
constructor(paragraph: text.Paragraph, overflowClipRects: Array<common2D.Rect>) {
super();
this.paragraph = paragraph;
this.overflowClipRects = overflowClipRects;
}
paragraph: text.Paragraph;
overflowClipRects: Array<common2D.Rect>;
async draw(context: DrawContext) {
if (this.overflowClipRects.length != 0) {
context.canvas.saveLayer();
for (let index = 0; index < this.overflowClipRects.length; index++) {
const overflowClipRect = this.overflowClipRects[index];
context.canvas.clipRect(overflowClipRect, drawing.ClipOp.DIFFERENCE);
}
}
this.paragraph.paint(context.canvas, 0, 0);
if (this.overflowClipRects.length != 0) {
context.canvas.restore();
}
}
}
class TextNodeController extends NodeController {
private rootNode: FrameNode | null = null;
makeNode(uiContext: UIContext): FrameNode {
return this.rootNode ??= new FrameNode(uiContext)
}
addNode(node: RenderNode): void {
if (this.rootNode == null) {
return
}
const renderNode = this.rootNode.getRenderNode()
if (renderNode != null) {
renderNode.appendChild(node)
}
}
clearNodes(): void {
if (this.rootNode == null) {
return
}
const renderNode = this.rootNode.getRenderNode()
if (renderNode != null) {
renderNode.clearChildren()
}
}
}
自定义组件的自定义布局
鸿蒙中,可以通过自定义布局代码,来重新布局子组件的位置。
自定义组件的自定义布局-自定义组件-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者
以下是一个简单的例子, onMeasureSize
用于计算得到子组件的大小,onPlaceChildren
用于将子组件放置到对应的位置。
@Component
export struct CustomLayout {
@Builder
buildChildren() {
ForEach([1, 2, 3], (item: number, index: number) => {
Text('S' + item)
.fontSize(20)
.width(60 + 10 * index)
.height(100)
.borderWidth(2)
.margin({ left: 10 })
.padding(10)
})
}
onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>,
constraint: ConstraintSizeOptions): SizeResult {
for (let index = 0; index < children.length; ++index) {
let child = children[index];
let childResult: MeasureResult = child.measure({
minHeight: constraint.minHeight,
minWidth: constraint.minWidth,
maxWidth: constraint.maxWidth,
maxHeight: constraint.maxHeight
})
}
// 根据自己的情况返回整个容器的最终大小
return {
width: 100,
height: 100,
};
}
onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {
for (let index = 0; index < children.length; ++index) {
let child = children[index];
let x = 0;
let y = 0;
// 如果不想显示子组件,将它布局到较偏的位置,达到不显示的目的
// child.layout({ x: Infinity, y: Infinity });
child.layout({ x: x, y: y })
}
}
build() {
this.buildChildren()
}
}
buildChildren
在 buildChildren
方法中返回,需要布局的子组件。这里包括文本全部的占位组件,以及溢出效果组件。
在我们这里,需要布局的是文本中的 WidgetSpan
和 OverflowWidget
。
@Builder
builder() {
ForEach([...WidgetSpan.extractFromInlineSpan(this.text),
...(this.overflowWidget == undefined ? [] : [this.overflowWidget])], (item: WidgetBuilder, index: number) => {
item.builder.builder(item.builderArgs)
})
};
onMeasureSize
onMeasureSize?(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions): SizeResult
ArkUI
框架会在自定义组件确定尺寸时,将该自定义组件的节点信息和尺寸范围通过 onMeasureSize
传递给该开发者。不允许在 onMeasureSize
函数中改变状态变量。
- 首先我们将子组件都
measure
,获取到它们的真实大小。
let childCount = this.overflowWidget != null ? children.length - 1 : children.length;
let index = 0;
let dimensions = new Array<MeasureResult>();
for (; index < childCount; index++) {
const child = children[index];
let childResult: MeasureResult = child.measure({})
let margin = child.getMargin();
dimensions.push({
width: vp2px(childResult.width + margin.start + margin.end),
height: vp2px(childResult.height + margin.top + margin.bottom),
});
}
- 通过
InlineSpan
build
方法,将文本和WidgetSpan
占位添加到paragraphGraphBuilder
中。
this.text?.build(paragraphGraphBuilder, dimensions);
let paragraph = paragraphGraphBuilder.build();
paragraph.layoutSync(vp2px(maxWidth));
- 接下来就是通过
paragraph
,来计算是否溢出,根据用户的设置,计算出来OverflowWidget
组件所在的位置。 - 将计算好的
paragraph
和需要裁剪的区域,回调出去。
this.onDrawText(paragraph, this._overflowClipRects);
onPlaceChildren
onPlaceChildren?(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions):void
ArkUI
框架会在自定义组件布局时,将该自定义组件的子节点自身的尺寸范围通过 onPlaceChildren
传递给该自定义组件。不允许在 onPlaceChildren
函数中改变状态变量。
onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {
if (children.length == 0) {
return;
}
let paragraph = this._paragraph!;
let rects = paragraph.getRectsForPlaceholders();
let index = 0;
for (; index < rects.length; index++) {
let rect = rects[index].rect;
rect = {
left: px2vp(rect.left),
right: px2vp(rect.right),
top: px2vp(rect.top),
bottom: px2vp(rect.bottom),
}
let child = children[index];
let margin = child.getMargin();
let x = rect.left - margin.start;
let y = rect.top - margin.top;
if ((this._overflowWidgetRect != null && RectUtils.hasIntersection(rect, this._overflowWidgetRect!)) ||
// hide widget
(rect.right == rect.left && rect.top == rect.bottom)
) {
// 不显示
x = Infinity;
y = Infinity;
}
child.layout({ x: x, y: y });
}
if (this.overflowWidget != null) {
// overflow widget
let child = children[children.length-1];
if (this._overflowWidgetRect != null) {
child.layout({
x: this._overflowWidgetRect.left,
y: this._overflowWidgetRect.top,
})
} else {
// move out the screen
child.layout({ x: Infinity, y: Infinity })
}
}
}
- 通过
paragraph.getRectsForPlaceholders
,获取到全部的占位组件的位置,通过layout
方法将它们放置到对应的位置。 - 放置
overflowWidget
组件。
文本溢出算法
通过二分查找,找到 不溢出
和 溢出
临界点 X
。
开头 | 中间 | 结尾 |
---|---|---|
[0,X] | [m,X] ,m 为中间文本的索引位置 | 文本不需要改变,无需计算 |
该 Range
范围内的文本将不会显示。
比如 abcdef
, 我们找到的 Range
为 [2,3]
,即最终显示 ab...ef
。
考虑以后还要支持选择和复制,所以我们这里不能简单丢掉 cd
。我们将会保存当前 span
的开始 index
和实际的文本。
你见到的并不是真实的
TextSpan(
text: 'abef',
actualText: 'abcdef',
start: 0,
);
一些注意的点
Canvas
和组件单位不一致
Canvas
中的宽高,字号,距离等单位为 px
, 组件中的宽高,字号,距离等单位为 vp
。
px2vp
Canvas
到组件,我们需要调用px2vp
进行转换。vp2px
组件 到Canvas
,我们需要调用vp2px
进行转换。
属性不触发刷新
属性不在 build
方法体里面,当属性变化的时候不会触发刷新。
@Watch('reBuildTextSpan') @Prop specialTextSpanBuilder?: SpecialTextSpanBuilder;
@Watch('reBuildTextSpan') @Prop text?: string;
@Watch('reBuildTextSpan') @Prop textSpan?: InlineSpan;
@Watch('reBuildTextSpan') @Prop joinZeroWidthSpace: boolean = false;
private _building: boolean = false;
reBuildTextSpan(propName: string) {
if (!this._building) {
this.myNodeController.clearNodes();
this._building = true;
this._refreshTag = !this._refreshTag;
}
}
@State private _refreshTag: boolean = false;
我们使用新的一个属性 _refreshTag
来控制刷新,反复设置来触发 ui
的重新刷新。
build() {
Stack() {
NodeContainer(this.myNodeController)
if (this._refreshTag) {
ExtendedParagraph({
overflowWidget: this.overflowWidget,
paragraphStyle: this.paragraphStyle,
fontCollection: this.fontCollection,
text: this._buildTextSpan(),
onDrawText: (paragraph, overflowClipRects: Array<common2D.Rect>) => {
this.myNodeController.clearNodes();
let node = new MyRenderNode(paragraph, overflowClipRects);
node.size = {
width: paragraph.getMaxWidth(),
height: paragraph.getHeight(),
}
this.myNodeController.addNode(node);
this._building = false;
}
}).align(Alignment.Top).width('100%')
} else {
ExtendedParagraph({
overflowWidget: this.overflowWidget,
paragraphStyle: this.paragraphStyle,
fontCollection: this.fontCollection,
text: this._buildTextSpan(),
onDrawText: (paragraph, overflowClipRects: Array<common2D.Rect>) => {
this.myNodeController.clearNodes();
let node = new MyRenderNode(paragraph, overflowClipRects);
node.size = {
width: paragraph.getMaxWidth(),
height: paragraph.getHeight(),
}
this.myNodeController.addNode(node);
this._building = false;
}
}).align(Alignment.Top).width('100%')
}
}
}
结语
如果只是牵扯到 Canvas
,不同平台的迁移还是蛮简单的。目前,Harmony Candies 主要维护的是鸿蒙组件以及 Flutter
鸿蒙插件。
欢迎更多的开发者加入我们,不管你之前是做什么的,社区期待着你的加入。
关注公众号 糖果代码铺
,获取更多 鸿蒙/Flutter
动态信息。
爱 鸿蒙
,爱糖果
,欢迎加入Harmony Candies,一起生产可爱的鸿蒙小糖果QQ群:981630644