HarmonyOS Developer 基于ArkTS开发常见问题-UI开发
Shape组件设置填充色为透明(ArkTS)
可以使用Shape组件的fill属性实现。
.fill('rgba(0,0,0,0.0)')
具体的使用如下:
@Entry
@Component
struct Index {
build() {
Stack({ alignContent: Alignment.Bottom }) {
Text('First child, show in bottom').width('90%').height('100%').backgroundColor(0xd2cab3).align(Alignment.Top)
Text('Second child, show in top').width('70%').height('60%').backgroundColor(0xc1cbac).align(Alignment.Top)
Shape() {
Rect().width(300).height(50)
Ellipse().width(300).height(50).offset({ x: 0, y: 60 })
Path().width(300).height(10).commands('M0 0 L900 0').offset({ x: 0, y: 120 })
}
.viewPort({ x: -2, y: -2, width: 304, height: 130 })
.fill('rgba(0,0,0,0.0)')
.stroke(Color.Black)
.strokeWidth(4)
}.width('100%').height(150).margin({ top: 5 })
}
}
如何设置状态栏颜色(JS/ArkTS)
可以查阅“JS API参考 > 接口 > 图形图像 > 窗口”中的内容,使用setSystemBarProperties方法,设置状态栏颜色。
var SystemBarProperties={
statusBarColor: '#ff00ff',
navigationBarColor: '#00ff00',
//以下两个属性从API Version7开始支持
isStatusBarLightIcon: true,
isNavigationBarLightIcon:false
};
windowClass.setSystemBarProperties(SystemBarProperties, (err, data) => {
if (err) {
console.error('Failed to set the system bar properties. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in setting the system bar properties. Data: ' + JSON.stringify(data));
});
如何创建成对的key:value的Map变量(ArkTS)
ArkTS遵循ES6语法,支持创建Map对象。Map对象需要写在事件里面,参考如下代码:
@Entry
@Component
struct Index {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button('测试')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.onClick((event: ClickEvent) => { var myMap = new Map() myMap.set("test", 1); console.info('Map test:'+myMap.get("test")) })
}.width('100%').height('100%')
}
}
List开发如何获取其滚动时Y轴偏移量(ArkTS)
参考示例代码如下:
@Entry
@Component
struct Index {
scroller: Scroller = new Scroller()
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
build() {
Stack({ alignContent: Alignment.TopStart }) {
Scroll(this.scroller) {
Column() {
ForEach(this.arr, (item) => {
Text(item.toString())
.width('90%')
.height(150)
.backgroundColor(0xFFFFFF)
.borderRadius(15)
.fontSize(16)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
}, item => item)
}.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.On)
.scrollBarColor(Color.Gray)
.scrollBarWidth(30)
.onScroll((xOffset: number, yOffset: number) => {
console.info("水平偏移量: " + xOffset + ' ' + "竖直偏移量: " + yOffset)
})
.onScrollEdge((side: Edge) => {
console.info('To the edge')
})
.onScrollEnd(() => {
console.info('Scroll Stop')
})
}.width('100%').height('100%').backgroundColor(0xDCDCDC)
}
}
图片资源放置目录(JS)
图片除存放在media目录下外,还可以存在在自定义目录下。
如,将图片资源icon.png放置在自定义创建的“entry/src/main/js/MainAbility/common/img”目录下,则可以在index.js中使用,示例代码如下:
// index.js
<image src="../../common/img/icon.png"></image>
video组件无法播放音频(JS)
使用<video>可以实现音频播放,注意资源文件引入路径是否正确:
<video src="/common/test.mp3">audio</video>
使用npm引入三方包(JS)
- 方法一:
a. 打开Terminal窗口,通过如下指令进入到entry目录。
cd entry
b. 以引入“dayjs”为例,执行以下指令进行安装。
npm install dayjs --save
c. 在对应的js文件中直接引用。
import dayjs from 'dayjs';
方法二:
a. 打开工程目录下的entry目录,找到该目录下的package.json文件。
b. 在package.json文件中写入想要安装的三方npm,以“dayjs”为例,示例如下:
{
"dependencies": {
"dayjs": "^1.10.4",
}
}
c. 打开Terminal窗口,通过如下指令进入到entry目录。
cd entry
d. 执行指令进行安装。
npm install
e. 在对应的js文件中直接引用。
import dayjs from 'dayjs';
说明
仅支持引入js三方包。
引用js代码文件时使用工程下的绝对路径(JS)
为避免采用相对路径引入造成的引用路径过长,阅读性和维护性较差的问题,可以使用工程根目录或entry下的绝对路径进行引用,如:
判断方法是否为异步方法(JS)
需要判断show方法是否为异步方法,可以调用以下接口,判断方法执行是否返回[object Promise]对象。如果返回,则为异步方法。
Object.prototype.toString.call()
示例如下:
export default {
data: {
title: 'World'
},
onInit() {
console.info(Object.prototype.toString.call(this.show()))
},
show: async function() {
}
}
多媒体资源加载失败(JS)
检查多媒体资源的放置路径,以视频文件“test_video.mp4”为例,需要将其放置到“js > default > common”目录下。
使用<image>标签引入的本地图片无法加载(JS)
使用<image>标签引入本地图片,但图片无法加载的可能情况有三种:
- 没有给图片设置宽度和高度,需要在对应的“page”目录下的css样式文件中设置图片的宽高。使用<image>标签的图片不会自动缩放,图片宽高超过组件的宽高会自动截取。
- 图片引入路径错误。图片引入的路径必须是项目编译后的静态文件的路径。
- 在导入图片或添加/删除页面后没有重新编译。
网络图片无法加载(JS/ArkTS)
使用图片资源时,应用可以成功加载common路径下的本地图片,但无法加载网络图片。检查是否申请相关权限ohos.permission.INTERNET,进行网络连接。
是否可以利用轻量级智能穿戴的物理按键开发应用的交互设计(JS)
轻量级智能穿戴设备的物理按键事件不支持上报给应用。
轻量级智能穿戴的文件句柄是否有上限(JS)
当前最多为50个。
关于轻量级智能穿戴k-v键值储存问题(JS)
- Value的值限制在128个字节,一个中文字符占用3个字节。
- 数据储存会放置为本地文件中(在项目目录下的kvstore\),其中key作为文件名,所以键名不能为空字符串。应用退出,不会删除这些文件。
轻量级智能穿戴开发如何获取dom中的元素(JS)
通过ref属性获取dom中的元素,详细示例如下。获取的元素只能使用它的方法,不能改变属性。
<!--index.hml-->
<div class="container">
<!--指定组件的ref属性为animator-->
<image-animator class="image-player" ref="ainmator" images="{{images}}" duration="1s" onclick="handleClick"></image-animator>
</div>
/* index.js */
export default {
data: {
images:[
{src:"common/frame1.png"},
{src:"common/frame2.png"},
{src:"common/frame3.png"}
]
},
handleClick(){
//通过$refs属性获取对应的组件,在hml中,组件的ref属性要设置为animator
const animator = this.$refs.animator;
const state = animator.getState();
if(state == "paused"){
animator.resume();
}else if(state == "stopped"){
animator.start();
}else{
animator.pause();
}
}
}
如何在页面间传值(JS)
通过router.replace方法中的params参数来传递,参考代码如下:
第一个页面传递参数:
router.replace({
uri: 'pages/details/details', // 要跳转的页面uri
params: { transferData: this.data } // 传递的数据,数据个数和名称开发者自己定义
});
第二个页面接收参数:
onInit(){
const data = this.transferData; // 在onInit函数中页面接收传递的数据
}
list如何滚动到某个item(JS)
通过list的scrollTo方法滚动到指定的item,参数是目标item的index。Index参数可以通过scrollend事件获取或者开发者指定。
使用如下方法:
this.$element('list').scrollTo({index:20, smooth: true})
轻量级智能穿戴text是否支持多行(JS)
text支持多行。通过回车键换行或者是不设置text的高度属性,由控件自动根据内容换行。
控件不显示(JS)
首先检查是否设置width和height值,除特殊情况外(text多行文字不需要设置高度),要求所有组件必须显式指定width和height值。
添加的页面不显示(JS)
确认是通过右键“新建文件”还是右键“New Page”创建的新页面,如果是“新建文件”创建的新页面,需要手动在配置文件config.json配置文件中添加新页面的配置信息;如果是“New Page”创建的页面,需要分析界面是否存在错误。
轻量级智能穿戴如何使用定时器(JS)
定时器最多同时支持三个,且在页面跳转时要显式释放定时器,否则框架只会在应用退出的时候才会去释放定时器。
轻量级智能穿戴的Left、Top为什么不生效(JS)
除根节点外,Left、Top配合Stack组件使用才有效果。
动态绑定为什么不生效(JS)
在进行绑定时,必须先将要绑定的对象或者对象的属性进行定义,不能先绑定后定义。
轻量级智能穿戴实现相对定位和绝对定位(JS)
使用div、stack(top left属性)来实现相对和绝对定位。
如何控制组件的显示与隐藏(JS)
通过display、show和if来控制控件的显示与隐藏。
区别在于:if为false时,组件会从VDOM中移除,而show仅仅是渲染时不可见,组件依然存在于VDOM中。
使用另一个js文件中的常量(JS)
若要使用data.js中的data1变量,首先需要将data1变量导出。
在data.js文件末尾加上如下内容:
export {
data1
}
然后在使用的文件头导入data.js文件。
import data from '../../common/js/data.js'
使用data.data1就可使用data1变量了。
控制块元素横纵向排列(JS)
如果想要块元素纵向排列,需要给父元素设置flex-direction: column;如果想要横向排列,需要给父元素设置flex-direction: row即可,通常容器组件flex-direction样式的缺省值即为row。
控制一个元素显示或隐藏(JS)
如果想要动态控制显示或隐藏某个元素,可以为元素添加show属性,然后为这个属性绑定js变量;若将变量值设为true,即可控制元素显示,设为false,则控制元素隐藏。
<div class="container" show="{{flag}}">
JS是否可以调用C++的so库(JS)
当前可以通过JS调用Java再通过JNI的方式调用C++。
JS模块export的module在别的JS文件中import,修改不生效(JS)
方舟开发框架的import是静态import,即编译过程会把互相依赖的文件合并以提高运行效率。如果一个JS模块被多个文件import,则会在不同的文件中分别生成变量。
如果需要共享变量,建议在app.js中声明,并通过getApp()全局方法获取该对象。
设置了border-left-radius属性后没有任何效果(JS)
设置圆角是需同时标明左右和上下,所以不能只设置一个left。需使用如下设置:border-top-left-radius或border-bottom-left-radius,来确定是上左还是下左圆角设置。
list在手表上有时候自动放大(JS)
list有一个itemscale属性,属性值类型为boolean,默认值为true,此属性控制焦点进出时list的放大缩小效果,且仅在智能穿戴和智慧屏上生效。若想取消手表上list放大缩小的效果,则将此属性设置为false即可。
如何使用list组件的scrollTo方法(JS)
使用scrollTo事件,可以使用this.$element("id").scrollTo这个方法去调用scrollTo事件,要特别注意,前文的"id"是元素的id值,而不是class名。
去掉list-item在按下时出现的阴影(JS)
可以通过设置clickeffect=false来取消点击动效。
video组件在预览时出现‘初始化播放器失败’(JS)
video组件不支持预览器预览,若要看实际效果,则需在模拟器或者真机上运行。
image组件切换图源(JS)
将image组件中的src属性用js中的变量绑定,如 src="{{imageSrc}},当需要更改图源时,仅需在js中将这个js变量值变更即可,如 imageSrc="/common/pic.png"。
整齐等宽地展示采用for循环的图片列表(JS)
问题现象
商品描述场景很多会采用长图切图后的小图来拼接显示,切图后的小图通常宽度一致,高度不一致。直接for循环套image会使得每个图片按照相同高度布局,最终效果会有白边(左右、上下)。
解决措施
实现布局图原图的效果,需要主动设置图片的宽高比。
<!-- xxx.hml -->
<image class="detail_image" src="{{$item}}" key="{{$idx}}" style="aspect-ratio:{{ratios[$idx]}};" oncomplete="testcomplete($idx)"/>
在xxx.js中增加变量:ratios:[1, 1, 1, 1, 1, 1, 1],数组长度取决于图片列表长度。
修正宽高比
testcomplete(idx, e) {
if (e.height <= 0) {
return;
}
var ratio = e.width / e.height;
this.ratios.splice(idx, 1, ratio)
}
不通过点击控制checkbox的勾选状态(JS)
通过给checkbox的checked属性绑定js变量"{{index}}",然后通过在js中操作index变量的true和false的值来选中和取消选中。
纵向排列文本(JS)
不直接支持文本纵向排列。若要实现此效果,则将text组件宽度与字体大小设置相同值即可。
文本超出时显示省略号(JS)
先设置一个最大行数,然后使用样式text-overflow:ellipsis,在文本超出时,即会用省略号表示。
通过js方法实现点击input组件并打开键盘的效果(JS)
给input组件设置id值,并为其设置focusable="true"的属性,然后在js方法中通过this.$element("inputID")取到input元素,然后调用focus方法即可:
this.$element("box").focus({focus:true})
API 8 slider的步长step设置为0.1之后实际依然为1(JS)
适用于:HarmonyOS SDK 3.2.5.3版本,API8 FA模型
API版本为8时,slider的步长step设置基础值为1。API版本为9时,slider的步长step可设置为0.1。
使用ForEach/LazyForEach渲染语法渲染组件,组件仅显示一个或者丢失部分子组件(ArkTS)
问题现象
使用ForEach/LazyForEach渲染语法渲染组件,组件仅显示一个或者丢失部分子组件。
可能原因
ForEach(
arr: any[],
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)
LazyForEach(
dataSource: IDataSource,
itemGenerator: (item: any) => void,
keyGenerator?: (item: any) => string
)
检查ForEach/LazyForEach渲染语法的第三个参数键值生成函数中,针对数组数据的每一个数据项生成的键值是否唯一,当数据项的键值重复时,仅渲染键值第一次出现时的子组件,其余重复键值的子组件不会再添加到父容器中进行渲染。
解决措施
确保ForEach/LazyForEach渲染语法的第三个参数针对数组数据中的每一个数据项都生成了唯一的字符串。
异常代码示例
@Entry
@Component
struct IndexOne {
@State arr: Array<Item> = [new Item(1, '1'), new Item(2, '2'), new Item(3, '3')];
build() {
Column() {
List() {
ForEach(this.arr, (item: Item) => {
ListItem() {
Text(item.id + item.val)
}
// 针对自定义类型对象,toString()方法在没有重写时默认返回"[object Object]",导致三个元素键值相同
// 该场景下可以修改为:item => JSON.stringify(item)解决
}, (item: Item) => item.toString() )
}
}
}
修改ForEach使用的数据对象,UI不刷新(ArkTS)
问题现象
修改ForEach使用的数据对象,UI不刷新。
解决措施
- 确保在代码中ForEach使用的数据对象已被状态变量修饰。
- 确保数据变化前后ForEach生成的子组件键值发生了变化。
异常代码示例
class Item {
id: number;
val: string;
constructor(id: number, val: string) {
this.id = id;
this.val = val;
}
}
@Entry
@Component
struct IndexOne {
@State arr: Array<Item> = [new Item(1, '1'), new Item(2, '2'), new Item(3, '3')];
build() {
Column() {
Button('change second value')
.onClick(()=>{
// 修改数据内容时Item对象的id没有发生变化,导致item.id.toString()在数组变化前后没有改变,界面不刷新。
// 修改为:this.arr[1] = new Item(4, '4')
this.arr[1] = new Item(2, '4')
})
List() {
ForEach(this.arr, (item: Item) => {
ListItem() {
Text(item.id + item.val)
}
}, (item: Item) => item.id.toString() )
}.width('100%').height(300)
}
}
}
使用LazyForEach渲染语法渲染组件,UI不刷新(ArkTS)
问题现象
使用LazyForEach渲染语法渲染组件,组件仅显示一个或者空白
解决措施
- 确保在代码中使用了LazyForEach的DataChangeListener对象的更新方法来触发页面更新。
- 确保数据变化前后LazyForEach生成的子组件键值发生了变化。
异常代码示例
class MyDataSource implements IDataSource {
private listener: DataChangeListener
// 初始化数据列表
private dataArray: Array<{
id: number,
val: String
}> = [{ id: 0, val: '/path/image0.png' }, { id: 1, val: '/path/image1.png' }, {
id: 2,
val: '/path/image2.png'
}]
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener
}
unregisterDataChangeListener(listener: DataChangeListener): void {
this.listener = undefined
}
public changeSecondData(data: string): void {
this.dataArray[1].val = data
// 已经正常使用了onDataChanged函数通知了更新。
this.listener.onDataChange(1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List({ space: 3 }) {
ListItem() {
Text('change second value')
}.onClick(() => {
this.data.changeSecondData('new data')
})
LazyForEach(this.data, (item) => {
ListItem() {
Text(item.val).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
// changeSecondData()函数中没有修改id的值,导致下面的键值在变化前后未发生变化。
// 可以修改键值生成器函数:item => JSON.stringify(item)或者在changeSecondData()函数中同步修改id的值。
}, item => item.id.toString())
}
}
}
使用LazyForEach嵌套LazyForEach(或ForEach)显示异常(ArkTS)
该场景不再支持,可以通过如下方式实现嵌套类场景:
方式1: 使用一个LazyForEach进行实现:
class MyDataSource implements IDataSource {
private listener: DataChangeListener
// 构建嵌套场景下的二维数据列表
private dataArray: Array<Array<number>>;
public initialize() {
// 初始化二维数据列表
this.dataArray = new Array();
for (let a: number = 0; a < 5; ++a) {
this.dataArray[a] = new Array();
for (let b: number = 0; b < 5; ++b) {
this.dataArray[a][b] = a * 5 + b;
}
}
}
public totalCount(): number {
return 5 * 5
}
public getData(index: number): any {
// 基于二维数组的行列计算index对应的位置及数据。
return this.dataArray[Math.floor(index / 5)][index % 5]
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener
}
unregisterDataChangeListener(listener: DataChangeListener): void {
this.listener = undefined
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
aboutToAppear() {
this.data.initialize();
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: number) => {
ListItem() {
Row() {
Text(item.toString()).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
})
}.width('100%').height('100%')
}
}
方式2:在LazyForEach和LazyForEach嵌套中新增自定义组件进行桥接过渡:
// MyDataSource处理二维数据的行数据信息。
class MyDataSource extends MyBaseDataSource {
private dataArray: Array<number> = [1, 2, 3, 4, 5];
public totalCount(): number {
return 5;
}
public getData(index: number): any {
return this.dataArray[index];
}
}
// ChildDataSource通过二维数据的行信息处理二维数据的列数据。
class ChildDataSource extends MyBaseDataSource {
private rowNum: number;
private dataArray: Array<number> = [1, 2, 3, 4, 5];
constructor(rowNum: number) {
this.rowNum = rowNum;
}
public totalCount(): number {
return 5;
}
public getData(index: number): any {
return this.dataArray[index] + 5 * this.rowNum;
}
}
@Component
struct ChildComponent {
private data: ChildDataSource;
private rowNum: number;
aboutToAppear() {
this.data = new ChildDataSource(this.rowNum)
}
build() {
Column() {
// 基于二维数据的行信息构建列数据对应的子组件。
LazyForEach(this.data, (item) => {
Row() {
Text(item.toString()).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
})
}
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: number) => {
// 构建二维数据行信息对应的子组件。
ListItem() {
ChildComponent({ rowNum: item - 1 })
}
})
}.width('100%').height('100%')
}
}
方式3:使用ForEach嵌套ForEach的替代
@Entry
@Component
struct MyComponent {
@State data: Array<Array<number>> = []
aboutToAppear() {
// 初始化二维数据列表
this.data = new Array();
for (let a: number = 0; a < 5; ++a) {
this.data[a] = new Array();
for (let b: number = 0; b < 5; ++b) {
this.data[a][b] = a * 5 + b;
}
}
}
build() {
List({ space: 3 }) {
ForEach(this.data, (item: Array<number>) => {
// 使用ForEach嵌套结构
ForEach(item, (item: number) => {
ListItem() {
Row() {
Text(item.toString()).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
})
})
}.width('100%').height('100%')
}
}
IF条件变化后UI不刷新(ArkTS)
问题现象
IF条件变化后UI不刷新,异常代码示例如下:
@Entry
@Component
struct Page {
@State title: string = 'Hello'
showMessage: Boolean = false;
build() {
Row() {
Column() {
// showMessage 为常规变量,他的更新不应该引起if组件的重新渲染,显示Text组件'Hello World'
Text(this.title)
.fontSize(50)
.onClick(() => {
this.showMessage = true;
this.title = 'Show message'
})
if (this.showMessage) {
Text('Hello World')
.fontSize(50)
}
}
}
.height('100%')
}
}
解决措施
确保IF条件中使用了状态变量。排查状态变量渲染更新失效,一定要排查当前组件是否有状态变量关联了它,如果没有,则它在初始化后,永远不会重新渲染更新。