
#我的鸿蒙开发手记#鸿蒙NEXT:给列表结构增加半模态弹框避免bug复杂数据不用ObjectLink也可 原创
写在前面
在业务开发中,常遇到各种列表,并给表中每个元素添加点击弹窗,在弹窗内查看每个元素的其他信息或操作,如前两天我的一位前同事问我的一个项目上的问题,商品列表中点击商品元素出现半模态,展示些其他简略信息等场景.
今天我们以商品列表做实力,逐步看下常规最先想到的写法,并逐步看到遇到的情况与解决思路.
案例代码
我们先看下,常规想到的使用bindsheet半模态结构,加载列表元素上,这样点击商品会出现半模态,并使用ForEach进行列表渲染,
我们首先定义一个类和造出数据:
interface IListTest {
id: number;
name: string;
isShow:boolean
testDesc:string
}
@State listTest: IListTest[] = [];
@State isShowSheet:boolean = false;
aboutToAppear(): void {
this.listTest = [
{
id: 1,
name: 'test1',
isShow:false,
testDesc:'test1富商大贾身高多少'
},
{
id: 2,
name: 'test2',
isShow:false,
testDesc:'test2大哥打电话大后天'
},
{
id: 3,
name: 'test3',
isShow:false,
testDesc:'test3烧过水勾三搭四订个蛋糕'
},
{
id: 4,
name: 'test4',
isShow:false,
testDesc:'test4小王小李小张'
},
{
id: 5,
name: 'test5',
isShow:false,
testDesc:'test5小王小李小张'
}
]
}
渲染
Column() {
List(){
ForEach(this.listTest, (item:IListTest,index) => {
ListItem(){
Row(){
Image($r('app.media.startIcon'))
.width(50)
.aspectRatio(1)
Column(){
Text(item.name)
.fontSize(20)
.fontColor(Color.Red)
Text(item.testDesc)
.fontSize(15)
.fontColor(Color.Black)
}
}
.width('100%')
.border({width:1,color:Color.Black,radius:20})
.padding(10)
.backgroundColor(Color.White)
.onClick(()=>{
item.isShow = true;
// this.listTest[index].isShow = true;
// this.listTest.splice(1,1,this.listTest[1])
})
// .bindSheet(this.listTest[index].isShow, this.SheetTest(item,index),{
.bindSheet(item.isShow, this.SheetTest(item,index),{
showClose:false,
height:'50%'
})
}.margin(10)
},
(item:IListTest,index:number)=>{
return JSON.stringify(item.isShow)+index
}
)
}
}
.height('100%')
.width('100%')
给bindSheet建立一个弹出的结构,读者可以自行定义内部UI代码
@Builder SheetTest(item:IListTest,index:number){
Column() {
Text('关闭').width('100%')
.fontSize(25)
.textAlign(TextAlign.End)
.onClick(() => {
console.log('pkl--item.isShow',item.isShow)
item.isShow = false;
console.log('pkl--item.isShow',item.isShow)
// this.listTest[index].isShow = false;
// this.listTest.splice(1,1,this.listTest[1])
})
Text(item.name)
.fontSize(25)
Text(item.testDesc) .fontSize(20)
}.width('100%')
.padding(15)
}
这里我们使用item.isShow来控制半模态的显隐,因为定义一个布尔值传入这里会导致全都弹出来,我们就不举例了,有时间的同学可以试一下哦.
.bindSheet(item.isShow, this.SheetTest(item,index),{
showClose:false,
height:'50%'
})
注意这里第三个参数对象是控制半模态的参数,这里我设置了showClose:false关闭半模态自带的x按钮,表示不显示关闭按钮,height:'50%'表示弹窗高度50%,
好了,以上你应该可以渲染得到一个列表结构了,我们使用每个元素对象中的isShow传给bindsheet来控制各自的模态显隐,但你会发现半模态一个都弹不出来,但通过打印我们可以看到每个元素内的isShow的值是已经发生了改变的.
这是因为,我们列表对象不是简单类型数据,而ArkTS的响应式,响应监听不到,所以页面就没有变化,那么怎么能让他感受到呢?
如果是初级鸿蒙开发,肯定也能想到这一步,那就让数据变化在第一层,即让数据以元素为单位变化,即删除一个元素并将刚刚删除的元素再插入对应位置,还记得我们上篇分享的数组的处理方法吗,使用splice来处理.
那么在元素下的click时间中做如下改变,就达到了弹窗的实现.并且各个弹窗不会同时弹出.
Row(){
Image($r('app.media.startIcon'))
.width(50)
.aspectRatio(1)
Column(){
Text(item.name)
.fontSize(20)
.fontColor(Color.Red)
Text(item.testDesc)
.fontSize(15)
.fontColor(Color.Black)
}
}
.width('100%')
.border({width:1,color:Color.Black,radius:20})
.padding(10)
.backgroundColor(Color.White)
.onClick(()=>{
console.log('pkl--item.isShow',item.isShow)
// item.isShow = true;
this.listTest[index].isShow = true;
this.listTest.splice(1,1,this.listTest[1])
console.log('pkl--item.isShow',item.isShow)
})
// .bindSheet(this.listTest[index].isShow, this.SheetTest(item,index),{
.bindSheet(this.listTest[index].isShow, this.SheetTest(item,index),{
showClose:false,
height:'50%'
})
对半模态内的UI结构,关闭下也进行改变数组,因为这里也是要改变数据来实现界面关闭.
@Builder SheetTest(item:IListTest,index:number){
Column() {
Text('关闭').width('100%')
.fontSize(25)
.textAlign(TextAlign.End)
.onClick(() => {
// item.isShow = false;
this.listTest[index].isShow = false;
this.listTest.splice(1,1,this.listTest[1])
})
Text(item.name)
.fontSize(25)
Text(item.testDesc) .fontSize(20)
}.width('100%')
.padding(15)
}
通过以上代码实现的效果如下,但是细心的同学发现商品元素中的图片,在半模态弹出和关闭时,会闪一下,因为我们使用的是让数组删掉一个元素再插入一个元素,数组重新加载,所以图片会闪一下,
那这显然是会影响用户体验的.
如何解决ForEach内渲染的多个元素中页面变化时图片闪一下的问题,代码优化
那么有没有一种方法可以那就是不改变数组来使用简单数据的变化来实现页面的刷新.
在此之前还有个重要知识点,那就是使用ForEach的第三个参数函数,较键值生成函数,经书上讲,如果鸿蒙识别到了键值函数的变化,也会引发刷新,
我们上面的代码其实已经加上了,如下
,
(item:IListTest,index:number)=>{
return JSON.stringify(item.isShow)+index
}
但其实并没有,他只是在渲染的时候对每个元素进行区分,但是对于非简单数据的数据变化,他并不会监听到.
所以,我们使用一个简单数据来控制ForEach的渲染,如下
@State isHoverText: Array<boolean> = new Array<boolean>(this.listTest.length).fill(false);
这样我们得到一个静态的数组,长度和listTest一样,初始值全为false,然后我们使用ForEach的参数函数,对每个元素进行判断,如果isHoverText[index]为true,则渲染弹出,否则关闭,如下
this.isHoverText[index]=false//true
在项目位置截图如下
这时候我们看到图片已经没有闪了,因为数组的元素数量并没有发生减少再补上,只是简单数据发生了改变.那么细心或者做到这一步的效果的小伙伴会发现一个bug.
那就是当我不点击半模态中自定义关闭按钮让对应的简单数据变为false时,对应的半模态是没有关闭的,因为点击空白区域是鸿蒙半模态自带的关闭效果,而我们没有使用$$符号在bindSheet中进行双向绑定.
因为我们的布尔值是在数组中,我们直接绑定会报错
最后bug的解决
.bindSheet($$this.listTest[index].isShow, this.SheetTest(item,index),)
这是因为鸿蒙内的一些组件双向绑定符号$$也只识别单个布尔值,所以这里我们使用bindSheet的第三个参数内的回调事件进行改变
.bindSheet(this.isHoverText[index], this.SheetTest(item,index),{
// .bindSheet(item.isShow, this.SheetTest(item,index),{
showClose:false,
height:'50%',
onDisappear:()=>{
this.isHoverText[index]=false
}
})
即在半模态关闭时,将isHoverText[index]的值改为false,这 样在点击空白区域半模态关闭时,对应的isHoverText[index]为false,对应的半模态也会关闭.
最终解决掉bug的效果如下啦
在这里可能会有同学提出使用objeclink装饰器来解决非简单数据的变化页面刷新,我们后面会单独讲这个哦,这里主要给不想或者不会ObjectLink装饰器的同学的一种解决方案.
总结
1-我们使用bindSheet的参数函数对ForEach的元素进行判断,如果为true则渲染半模态,反之关闭半模态;
2-我们使用每个对象的内字段isShow进行控制时,发现鸿蒙并不能监听到,页面没有弹出变化,于是使用改变数组元素数量的方式实现了弹出,但是由于数组元素是先减后增加的,所以图片会闪一下,所以这里使用一个简单数据来控制ForEach的渲染,然后使用参数函数对ForEach的元素进行判断,如果为true则渲染半模态,反之关闭半模态;
3-我们使用的简单数据类型来做一个商品数组的映射从而实现了页面的变化也没出现数据图片闪一下的bug,但是当我们点击空白区域关闭半模态时,对应的简单数据并没有改变.于是我们
使用bindSheet的 回调来时对应的简单数组值为false.实现了回调.
4-我们在这个过程中发现鸿蒙的双向绑定数据变化是针对最基础的简单数据的,如单个布尔值,单个数字等,对数组中的布尔值字段是不能直接使用$$符号进行双向绑定.
如果你看到这里如果还对你在项目中有所启发的话,点个赞再走吧.嘿嘿就很棒.
