#HarmonyOS NEXT体验官# 基于鸿蒙Next的App(创意工坊)解析4:插入文本 原创
在这篇文章中,我们会详细讲解如何实现《创意工坊》中的插入文字的功能,效果如下图所示:
首先,插件文本需要弹出如下图的设置对话框。
这个对话框需要使用InsertText组件完成,代码如下:
// 导入背景颜色和按钮背景颜色常量
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const'
// 导入文档大小相关的类型
import { DocumentSize, DocumentSizes } from '../data/Documents'
// 导入EntryAbility类
import EntryAbility from '../entryability/EntryAbility'
// 导入一些通用函数
import { documentExists, createDocumentDir, getInverseColor, rgbaToHex } from '../common/common'
// 导入Web颜色选择器对话框组件
import { WebColorPickerDialog } from './WebColorPickerDialog';
// 导入ObjConfig类型
import {ObjConfig} from '../common/types';
// 使用@Component装饰器标记这是一个自定义组件
@Component
// 导出InsertText结构体
export struct InsertText {
// 定义插入文本成功的回调函数
insertTextOK?: (textConfig: ObjConfig) => void
// 定义编辑文本成功的回调函数
editTextOK?: (textConfig: ObjConfig) => void
// 定义取消插入文本的回调函数
insertTextCancel?: () => void
// 使用@Link装饰器定义双向绑定的配置对象
@Link config:ObjConfig;
// 使用@Link装饰器定义双向绑定的插入或编辑状态
@Link insertOrEdit:boolean;
// 创建填充颜色选择器的控制器
fillController: CustomDialogController = new CustomDialogController({
builder: WebColorPickerDialog({
onClickEvent: this.onFillClickEvent.bind(this)
}),
})
// 创建边框颜色选择器的控制器
strokeController: CustomDialogController = new CustomDialogController({
builder: WebColorPickerDialog({
onClickEvent: this.onStrokeClickEvent.bind(this)
}),
})
// 定义边框颜色点击事件处理函数
onStrokeClickEvent(color: string) {
// 如果选择的颜色不为空
if (color.trim() != '') {
// 将RGBA颜色转换为十六进制并设置到配置对象中
this.config.stroke = rgbaToHex(color);
}
// 关闭边框颜色选择器
this.strokeController.close();
}
// 定义填充颜色点击事件处理函数
onFillClickEvent(color: string) {
// 如果选择的颜色不为空
if (color.trim() != '') {
// 将RGBA颜色转换为十六进制并设置到配置对象中
this.config.fill = rgbaToHex(color);
}
// 关闭填充颜色选择器
this.fillController.close();
}
// 定义build函数,用于构建组件的UI
build() {
// 创建一个垂直布局的列
Column() {
// 创建标题文本
Text('编辑文本属性')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100')
.height('30')
.textAlign(TextAlign.Center)
.margin({ bottom: 5, top: 10 })
// 创建文本输入框
TextInput({text:this.config.text,placeholder:'请输入要插入的文本'}).fontSize(16)
.width(200)
.height(40)
.margin({top:10})
.fontColor('#000000')
.backgroundColor('#FFFFFF')
.onChange((value: string) => {
// 当输入内容改变时,更新配置对象中的文本
this.config.text = value;
})
// 创建一个水平布局的行,用于填充颜色选择
Row() {
// 创建填充颜色按钮
Button({ type: ButtonType.Normal }) {
Text('填充颜色')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('30')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width(100)
.height(30)
.onClick(() => {
// 点击按钮时打开填充颜色选择器
this.fillController.open();
});
// 显示当前填充颜色
Text(this.config.fill) {
}
.fontSize(16)
.width('100')
.height('30')
.margin({ left: 5 })
.foregroundColor(getInverseColor(this.config.fill))
.backgroundColor(this.config.fill);
}.margin({ top: 10 })
// 创建一个水平布局的行,用于边框颜色选择
Row() {
// 创建边框颜色按钮
Button({ type: ButtonType.Normal }) {
Text('边线颜色')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('30')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width(100)
.height(30)
.onClick(() => {
// 点击按钮时打开边框颜色选择器
this.strokeController.open();
});
// 显示当前边框颜色
Text(this.config.stroke) {
}
.fontSize(16)
.width('100')
.height('30')
.margin({ left: 5 })
.foregroundColor(getInverseColor(this.config.stroke))
.backgroundColor(this.config.stroke);
}.margin({ top: 10 })
// 创建一个水平布局的行,用于显示边框宽度
Row() {
Text('边线宽度')
.fontSize(16)
.fontColor('#FFFFFF')
.width('80')
.height('30')
.textAlign(TextAlign.Center)
Text(`${this.config.strokeWidth}`)
.fontSize(16)
.fontColor('#FFFFFF')
.width('50')
.height('30')
.textAlign(TextAlign.Center)
}.margin({ top: 10 })
// 创建边框宽度滑动条
Slider({
value: this.config.strokeWidth,
min: 0,
max: 10,
step: 1,
style: SliderStyle.OutSet
})
.margin({ left: 10, right: 10 })
.onChange((value: number, mode: SliderChangeMode) => {
// 当滑动条值改变时,更新配置对象中的边框宽度
this.config.strokeWidth = Math.round(value);
})
// 创建一个水平布局的行,用于显示字体大小
Row() {
Text('字体尺寸')
.fontSize(16)
.fontColor('#FFFFFF')
.width('80')
.height('30')
.textAlign(TextAlign.Center)
Text(`${this.config.fontSize}`)
.fontSize(16)
.fontColor('#FFFFFF')
.width('50')
.height('30')
.textAlign(TextAlign.Center)
}.margin({ top: 10 })
// 创建字体大小滑动条
Slider({
value: this.config.fontSize,
min: 10,
max: 100,
step: 1,
style: SliderStyle.OutSet
})
.margin({ left: 10, right: 10 })
.onChange((value: number, mode: SliderChangeMode) => {
// 当滑动条值改变时,更新配置对象中的字体大小
this.config.fontSize = Math.round(value);
})
// 创建一个弹性布局,用于上划线选项
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
// 创建上划线复选框
Checkbox({ name: 'overline' })
.selectedColor(0x39a2db)
.select(this.config.overline)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
// 当复选框状态改变时,更新配置对象中的上划线状态
this.config.overline = value;
})
.mark({
strokeColor: Color.Black,
size: 50,
strokeWidth: 5
})
.width(25)
.height(25)
// 创建上划线文本
Text('上划线').fontSize(16).foregroundColor('#FFFFFF')
.onClick(() =>{
// 点击文本时切换上划线状态
this.config.overline = !this.config.overline;
})
}
.margin({ top: 10,left:10 })
// 创建一个弹性布局,用于删除线选项
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
// 创建删除线复选框
Checkbox({ name: 'linethrough' })
.selectedColor(0x39a2db)
.select(this.config.linethrough)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
// 当复选框状态改变时,更新配置对象中的删除线状态
this.config.linethrough = value;
})
.mark({
strokeColor: Color.Black,
size: 50,
strokeWidth: 5
})
.width(25)
.height(25)
// 创建删除线文本
Text('删除线').fontSize(16).foregroundColor('#FFFFFF')
.onClick(() =>{
// 点击文本时切换删除线状态
this.config.linethrough = !this.config.linethrough;
})
}
.margin({ top: 10,left:10 })
// 创建一个弹性布局,用于下划线选项
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
// 创建下划线复选框
Checkbox({ name: 'underline' })
.selectedColor(0x39a2db)
.select(this.config.underline)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
// 当复选框状态改变时,更新配置对象中的下划线状态
this.config.underline = value;
})
.mark({
strokeColor: Color.Black,
size: 50,
strokeWidth: 5
})
.width(25)
.height(25)
// 创建下划线文本
Text('下划线').fontSize(16).foregroundColor('#FFFFFF')
.onClick(() =>{
// 点击文本时切换下划线状态
this.config.underline = !this.config.underline;
})
}
.margin({ top: 10,left:10 })
// 创建一个水平布局的行,用于确定和取消按钮
Row() {
// 创建确定按钮
Button({ type: ButtonType.Normal }) {
Text('确定')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('30')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width(100)
.height(30)
.margin({right:10})
.onClick(() => {
// 点击确定按钮时,根据插入或编辑状态调用相应的回调函数
if(this.insertOrEdit) {
if (this.insertTextOK) {
this.insertTextOK(this.config);
}
} else {
if (this.editTextOK) {
this.editTextOK(this.config);
}
}
});
// 创建取消按钮
Button({ type: ButtonType.Normal }) {
Text('取消')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('30')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width(100)
.height(30)
.onClick(() => {
// 点击取消按钮时,调用取消回调函数
if(this.insertTextCancel) {
this.insertTextCancel();
}
}).margin({left:10});
}.margin({top:20,left:10,right:10})
}.backgroundColor(PANEL_BACKGROUND_COLOR)
.border({ width: 0, color: '#000000', radius: 10 })
.width('100%')
.height('100%')
}
}
下面详细解释一下这段代码
这段代码是一个名为InsertText的ArkTS自定义组件,用于实现文本插入和编辑的用户界面。我会非常详细地解释这段代码的结构和功能:
1.导入声明:
代码开始部分导入了必要的常量、类型和函数。这包括背景颜色常量、文档大小相关类型、一些通用函数(如颜色转换函数),以及一个自定义的Web颜色选择器对话框组件。
2.组件定义:
使用@Component装饰器定义了InsertText结构体,这是ArkTS中声明自定义组件的方式。
3.属性和方法:
- 定义了三个可选的回调函数:insertTextOK、editTextOK和insertTextCancel,用于处理文本插入、编辑和取消操作。
- 使用@Link装饰器定义了两个双向绑定属性:config(ObjConfig类型)和insertOrEdit(布尔类型)。这允许组件与父组件共享和同步数据。
- 创建了两个CustomDialogController实例:fillController和strokeController,用于管理填充颜色和边框颜色的选择器对话框。
- 定义了onStrokeClickEvent和onFillClickEvent方法,用于处理颜色选择后的逻辑,包括更新配置和关闭对话框。
4.UI构建(build方法):
build方法定义了组件的用户界面,使用ArkTS的声明式UI语法。主要包含以下元素:
a. 文本输入:
提供一个TextInput组件,允许用户输入或编辑文本。
b. 颜色选择:
为填充颜色和边框颜色各提供一个按钮和颜色显示区域。点击按钮会打开相应的颜色选择器对话框。
c. 边框宽度和字体大小调整:
使用Slider组件允许用户通过滑动来调整边框宽度和字体大小。当前值显示在滑动条旁边。
d. 文本装饰选项:
提供了三个Checkbox组件,分别用于选择上划线、删除线和下划线。每个选项旁边有一个文本标签,点击标签也可以切换选项状态。
e. 确定和取消按钮:
在界面底部提供了确定和取消按钮。确定按钮会根据当前是插入还是编辑模式调用相应的回调函数,而取消按钮会调用取消回调函数。
5.样式和布局: - 整个界面使用Column布局,将各个元素垂直排列。
- 使用Row和Flex布局来组织水平方向的元素,如颜色选择器和按钮。
- 应用了一些通用样式,如字体大小、颜色、边距等,以确保界面的一致性和美观性。
- 使用PANEL_BACKGROUND_COLOR作为整个面板的背景色,并添加了圆角边框。
6.交互逻辑: - 文本输入、滑动条、复选框等UI元素都绑定了相应的事件处理函数,用于实时更新config对象中的相关属性。
- 颜色选择使用自定义的WebColorPickerDialog组件,通过CustomDialogController来管理其显示和隐藏。
- 确定按钮根据insertOrEdit的值来决定调用insertTextOK还是editTextOK回调函数,传递更新后的config对象。
- 取消按钮调用insertTextCancel回调函数,允许父组件处理取消操作。
这个组件提供了一个全面的文本样式编辑界面,包括文本内容、颜色、大小、边框和装饰等多个方面的定制选项。它的设计考虑了用户交互的便利性,并通过回调函数和数据绑定实现了与父组件的良好集成。这使得它可以轻松地嵌入到需要文本编辑功能的更大型应用中。
当点击“确定”按钮插入文本时,会调用insertTextOK函数,代码如下:
// insertTextOK 函数用于处理文本插入操作
// 参数 textConfig 是一个 ObjConfig 类型的对象,包含了要插入文本的所有配置信息
insertTextOK(textConfig: ObjConfig) {
// 使用 formatObjectProperties 函数将 textConfig 对象转换为格式化的字符串
// 这可能是为了确保对象属性被正确序列化,以便在 JavaScript 中使用
let configText = formatObjectProperties(textConfig);
// 使用 controller1 对象运行 JavaScript 代码
// addText 是一个在 WebView 或其他 JavaScript 环境中定义的函数
// 我们将格式化后的配置文本作为参数传递给这个函数
this.controller1.runJavaScript(`addText(${configText})`)
.then(data => {
// 当 JavaScript 代码执行完毕后,这个 Promise 会被解决
// data 参数可能包含 addText 函数的返回值,但在这里没有被使用
// 调用 toggleInsertTextPanel 方法
// 这可能是用来隐藏或关闭文本插入面板的函数
this.toggleInsertTextPanel();
});
// 注意:如果 JavaScript 执行过程中出现错误,这里没有进行错误处理
}
在这段代码中,核心的函数就是用js实现的addText函数,用于添加文本,代码如下:
// addText 函数用于在 Fabric.js canvas 上添加可交互的文本对象
function addText(config) {
// 定义默认配置对象
var defaultConfig = {
left: 20, // 文本左侧距离画布左边的像素数
top: 20, // 文本顶部距离画布顶部的像素数
fontFamily: 'Arial', // 字体族
fill: '#333', // 文本填充颜色
fontSize: 20, // 字体大小(像素)
textAlign: 'left', // 文本对齐方式
lineHeight: 1.16, // 行高
charSpacing: 0, // 字符间距
fontWeight: 'normal', // 字体粗细
fontStyle: 'normal', // 字体样式(如斜体)
underline: false, // 是否添加下划线
overline: false, // 是否添加上划线
linethrough: false, // 是否添加删除线
editable: true, // 是否可编辑
stroke: null, // 文本描边颜色
strokeWidth: 1, // 描边宽度
shadow: null // 阴影设置
};
// 使用 Object.assign 合并用户提供的配置和默认配置
// 用户配置会覆盖默认配置中的同名属性
var settings = Object.assign({}, defaultConfig, config);
// 创建一个新的 fabric.IText 对象
// 如果 settings.text 存在则使用它,否则使用默认文本 'New text'
var text = new fabric.IText(settings.text || 'New text', settings);
// 将新创建的文本对象添加到 canvas 上
canvas.add(text);
}
addText函数的详细介绍如下:
1.函数定义:
addText 函数接受一个 config 参数,允许用户自定义文本对象的属性。
2.默认配置:
defaultConfig 对象定义了文本对象的默认属性,包括位置、字体、颜色、大小等样式设置。
3.配置合并:
使用 Object.assign 方法将用户提供的 config 与 defaultConfig 合并,允许用户覆盖默认设置。
4.创建 IText 对象:
使用合并后的配置创建一个新的 fabric.IText 对象。
5.添加到 Canvas:
将新创建的文本对象添加到 Fabric.js canvas 上。
关于 Fabric.js 中的 IText 类:
IText 是 Fabric.js 库中的一个交互式文本类,它继承自基本的 Text 类,但提供了更多的交互功能。以下是 IText 类的一些主要特点:
1.交互式编辑:
用户可以直接在画布上双击 IText 对象来编辑文本内容。
2.光标和选择:
支持文本光标和文本选择功能,就像在常规文本编辑器中一样。
3.样式编辑:
允许对文本的不同部分应用不同的样式,如颜色、字体大小等。
4.键盘事件:
响应键盘事件,支持常见的文本编辑操作,如删除、复制、粘贴等。
5.多行支持:
自动处理文本换行,支持多行文本。
6.富文本功能:
可以为文本的不同部分设置不同的样式,实现富文本效果。
7.动画支持:
可以轻松地为 IText 对象添加动画效果。
8.事件处理:
提供了多种事件钩子,允许开发者响应用户的交互行为。
IText 类使得在 Fabric.js 画布上创建和操作交互式文本变得非常简单。它特别适用于需要用户能够直接在画布上编辑文本的应用场景,如在线设计工具、图片编辑器等。通过提供的配置选项,开发者可以精细地控制文本的外观和行为,使其满足各种复杂的设计需求。