Badge角标组件
在目标组件的外层包裹一层Badge角标组件
支持位置:右上,左,右
也可以使用绝对定位实现更灵活的角标位置。
Badge({
count:1,//角标数值,角标数值为0时不展示
position:BadgePosition.RightTop,//角标位置,只有三个
style:{//角标样式
fontSize:14,
badgeSize:20,
badgeColor:'#fa2a2d'
}
}){
Image($r('app.media.bg_00'))
.width(100)
}
网格布局——Grid容器
在规则的行列布局中非常常见
ArkUI提供了Grid容器组件和子组件GridItem,用于构建网格布局。
布局与约束
Grid组件为网格容器,其中容器内各条目对应一个GridItem组件
说明:Grid的子组件必须是GridItem组件。
Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况:
-
行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐使用该种布局方式)
-
只设置行、列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动的方式展示。
-
行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。
设置排列方式
设置行列数量与占比
Grid组件提供了rowsTemplate和columnsTemplate
属性用于设置网格布局行列数量与尺寸占比。
rowsTemplate和columnsTemplate属性值是一个由多个空格和’数字+fr’间隔拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值大小,用于计算该行或列在网格布局宽度上的占比,最终决定该行或列宽度。
Grid() {
...
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 2fr 1fr')
说明: 当Grid组件设置了rowsTemplate或columnsTemplate时,Grid的layoutDirection、maxCount、minCount、cellLength属性不生效,属性说明可参考Grid-属性。
设置子组件所占行列数
在Grid组件中,可以通过创建Grid时传入合适的GridLayoutOptions实现单个网格横跨多行或多列的场景,其中,irregularIndexes
和onGetIrregularSizeByIndex
可对仅设置rowsTemplate或columnsTemplate的Grid使用;onGetRectByIndex
可对同时设置rowsTemplate和columnsTemplate的Grid使用。
在网格中,可以通过onGetRectByIndex返回的[rowStart,columnStart,rowSpan,columnSpan]
来实现跨行跨列布局,其中rowStart和columnStart属性表示指定当前元素起始行号和起始列号,rowSpan和columnSpan属性表示指定当前元素的占用行数和占用列数。
layoutOptions: GridLayoutOptions = {
regularSize: [1, 1],
onGetRectByIndex: (index: number) => {
if (index == key1) { // key1是“0”按键对应的index
return [5, 0, 1, 2]
} else if (index == key2) { // key2是“=”按键对应的index
return [4, 3, 2, 1]
}
// ...
// 这里需要根据具体布局返回其他item的位置
}
}
Grid(undefined, this.layoutOptions) {
// ...
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
设置主轴方向——layoutDirection
通过layoutDirection设置网格布局的主轴方向,决定子组件的排列方式。此时可以结合minCount和maxCount属性来约束主轴方向上的网格数量。
Grid() { ...}
.maxCount(3)
.layoutDirection(GridDirection.Row)
GridDirection.Row 先从左到右排列,排满一行再排下一行。
GridDirection.Column 先从上到下排列,排满一列再排下一列
说明
layoutDirection属性仅在不设置rowsTemplate和columnsTemplate时生效,此时元素在layoutDirection方向上排列。
仅设置rowsTemplate时,Grid主轴为水平方向,交叉轴为垂直方向。
仅设置columnsTemplate时,Grid主轴为垂直方向,交叉轴为水平方向。
在网格布局中显示数据——GridItem子组件
Grid组件可以通过二维布局的方式显示一组GridItem子组件。
对于内容结构相似的多个GridItem,通常更推荐使用ForEach语句中嵌套GridItem的形式,来减少重复代码。
@Entry
@Component
struct OfficeService {
@State services: Array<string> = ['会议', '投票', '签到', '打印']
build() {
Column() {
Grid() {
ForEach(this.services, (service:string) => {
GridItem() {
Text(service)
}
}, (service:string):string => service)
}
.rowsTemplate(('1fr 1fr') as string)
.columnsTemplate(('1fr 1fr') as string)
}
}
}
设置行列间距
通过Grid的rowsGap和columnsGap
可以设置网格布局的行列间距。
单位vp
Grid() {
...
}
.columnsGap(10)//列之间的间距,竖直的列
.rowsGap(15)//行之间的间距
构建可滚动的网格布局
在设置Grid的行列数量与占比时,如果仅设置行、列数量与占比中的一个,即仅设置rowsTemplate或仅设置columnsTemplate属性,网格单元按照设置的方向排列,超出Grid显示区域后,Grid拥有可滚动能力。
如果设置的是columnsTemplate,Grid的滚动方向为垂直方向;如果设置的是rowsTemplate,Grid的滚动方向为水平方向。
控制滚动位置
Grid组件初始化时,可以绑定一个Scroller对象,用于进行滚动控制,例如通过Scroller对象的scrollPage方法进行翻页。
private scroller: Scroller = new Scroller()
应用响应点击事件,通过指定scrollPage方法的参数next为true,滚动到下一页。
Column({ space: 5 }) {
Grid(this.scroller) {
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
Row({space: 20}) {
Button('上一页')
.onClick(() => {
this.scroller.scrollPage({
next: false
})
})
Button('下一页')
.onClick(() => {
this.scroller.scrollPage({
next: true
})
})
}
}
性能优化
与长列表的处理类似,循环渲染适用于数据量较小的布局场景,当构建具有大量网格项的可滚动网格布局时,推荐使用数据懒加载方式实现按需迭代加载数据,从而提升列表性能。
Grid组件中也可通过cachedCount属性设置GridItem的预加载数量,只在懒加载LazyForEach中生效。
设置预加载数量后,会在Grid显示区域前后各缓存cachedCount*列数个GridItem,超出显示和缓存范围的GridItem会被释放。
Grid() {
LazyForEach(this.dataSource, () => {
GridItem() {
}
})
}
.cachedCount(3)
说明
cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
案例
应用到了:网格布局、ForEach渲染、层叠布局、badge角标、遮罩显影、透明度层级与缩放动画、注册点击事件、随机数、控制展示(换图)、累加、反引号${}拼接字符串的使用
// 定义接口
interface ImageCount{
url: string,//图片地址
count: number//角标数值
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
//基于接口,准备数据
@State images: ImageCount[]=[
{url: 'app.media.bg_00',count: 0},
{url: 'app.media.bg_01',count: 0},
{url: 'app.media.bg_02',count: 0},
{url: 'app.media.bg_03',count: 0},
{url: 'app.media.bg_04',count: 0},
{url: 'app.media.bg_05',count: 0},
]
//控制遮罩层
@State maskOpacity: number = 0
@State maskZIndex: number = -1
//控制图片缩放
@State imageW: number = 0
@State imageH: number = 0
//取随机图片
@State randomIndex: number = -1
build() {
Stack(){
Column(){
Grid(){
ForEach(this.images,(item:ImageCount,index:number)=>{
GridItem(){
Badge({
count:item.count,
position:BadgePosition.RightTop,
style:{
fontSize:14,
badgeSize:20,
badgeColor:'#fa2a2d'
}
}){
Image($r(item.url))
.width(80)
}
}
})
}.columnsGap(10)//行间距
.rowsGap(10)
.layoutDirection(GridDirection.Row)
.maxCount(3)//主轴方向子组件存在最多的个数
.rowsTemplate('1fr 1fr')//控制均匀分布,两行x三列
.columnsTemplate('1fr 1fr 1fr')
.width('100%')
.height(300)
// .backgroundColor('#666')
// .border({
// width:2,
// color:Color.Red
// })
.margin({top:100})
.padding(5)
Button('立即抽卡')
.width(200)
.backgroundColor('#ed5b8c')
.margin({top:50})
.onClick(()=>{
this.maskOpacity = 1;
this.maskZIndex = 99;
//点击时图片缩放
this.imageW = 1;
this.imageH = 1;
//生成随机数
this.randomIndex = Math.floor(Math.random() * 6)//取【0,1,2,3,4,5】
})
}
.width('100%')
.height('100%')
//抽卡弹层
Column({space:10}){
Text('获得生肖卡')
.fontColor('#f5ebcf')
.fontSize(25)
.fontWeight(FontWeight.Bold)
Image($r(`app.media.img_0${this.randomIndex}`))
.width(200)
.scale({
x: this.imageW,
y: this.imageH
})
.animation({
duration:500
})
Button('开心收下')
.width(200)
.height(50)
.backgroundColor(Color.Transparent)
.border({
width:2,
color:'#fff9e8'
})
.onClick(()=>{
this.maskOpacity = 0;
this.maskZIndex = -1;
this.imageW = 0;
this.imageH = 0;
//对象数组更新,需要替换整个对象,只修改单个属性,不能更新
this.images[this.randomIndex] = {
url:`app.media.img_0${this.randomIndex}`,
count: this.images[this.randomIndex].count+1
}
})
}.width('100%')
.height('100%')
.backgroundColor('#b3000000')//前两位表示透明度
.justifyContent(FlexAlign.Center)
.opacity(this.maskOpacity)
.zIndex(this.maskZIndex)
.animation({//元素的动画显示
duration:100
})
}
}
}