鸿蒙开发Grid研究
概述:
本人新手入门鸿蒙开发,最近做房贷计算器小应用,在显示每一期数据列表的时候,用到了Grid,花费心思颇多,记录下核心点。
碰到一个麻烦就是鸿蒙不同版本少量API不兼容,如API19上的函数,不一定在API20上能用。网上找到的一些例子时常各种报错不能运行,这对新手是极不友好,拔高了入门门槛。
软硬件平台:
运行测试平台:鸿蒙6.0.0(Mate 70 Pro模拟器),即API20
开发平台Dev Echo:版本6.0.0.858
难点说明:
常用的Grid基本用法示例如下:
// 示例:创建一个2x2的网格布局
@Entry
@Component
struct GridExample {
build() {
Grid() {
GridItem() {
Text('Item 1')
.fontSize(16)
.textAlign(TextAlign.Center)
}
GridItem() {
Text('Item 2')
.fontSize(16)
.textAlign(TextAlign.Center)
}
GridItem() {
Text('Item 3')
.fontSize(16)
.textAlign(TextAlign.Center)
}
GridItem() {
Text('Item 4')
.fontSize(16)
.textAlign(TextAlign.Center)
}
}
.columnsTemplate('1fr 1fr') // 定义2列,每列宽度相等
.width('100%')
.height('100%')
}
}
根据实际需要,在这个基础上,需要解决以下问题:
1、文本显示宽度自适应
2、超出屏幕宽度,水平滚动
3、标题栏显示和内容列对齐,即宽度等宽水平滚动同步
4、不够屏幕宽居中显示
源码展示:
界面代码,夹杂对显示数字格式处理,在关键的地方写了注释
import { MeasureText, MediaQuery, router, window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { JmpPageAppState } from '../global/JmpPageAppState';
import EqPrincipalInterest from '../housemortgage/EqPrincipalInterest';
import { EqResultNode } from '../housemortgage/EqResultNode';
import IntegerFormat from '../util/IntegerFormat';
@Entry
@Component
struct ShowList {
/* 房贷每一期的信息,对象数组 */
@State listResult:EqResultNode[] = [];
/* 每一列宽度,总共有五列,默认最少20 */
@State cloumnFontWidth:number[] = [20, 20, 20, 20, 20];
/* 标题栏高度 */
@State s32TitleHeight:number = 20;
/* Grid宽度,后面会随着每一列宽度调整跟着改变 */
@State gridWidth:string = '100%';
/* 每一列显示的比例,后面会随着每一列宽度调整跟着改变 */
@State columnsTemplateStr:string = '2fr 1fr 1fr 1fr 1fr';
aboutToAppear() {
/* 获取计算好的房贷期数数据 */
this.listResult = JmpPageAppState.listResult;
}
showGridItemFormShowStr(item: EqResultNode, indexRow: number, indexColumn: number, inFontSize: number) {
let strShow: string = '';
let s32FontWidth:number = 0;
let s32I:number = 0;
let s32TotalWidth = 0;
let columnsTemplateStr:string = '';
/* 下面一堆操作表示0-4列显示处理,时间显示对齐和金额四舍五入加小数点 */
if (0 == indexColumn) {
strShow =
(indexRow + 1).toString() + ')' + Math.floor(item.s32YearMonth / 100).toString().padStart(4, '0') + '-' +
Math.floor(item.s32YearMonth % 100).toString().padStart(2, '0');
} else if (1 == indexColumn) {
strShow = IntegerFormat.ShowMoney(item.lInterest + item.lPrincipal);
} else if (2 == indexColumn) {
strShow = IntegerFormat.ShowMoney(item.lInterest);
} else if (3 == indexColumn) {
strShow = IntegerFormat.ShowMoney(item.lPrincipal);
} else if (4 == indexColumn) {
strShow = IntegerFormat.ShowMoney(item.lRemindAmount);
} else {
strShow = '';
}
/* 测量每一方格显示文本宽度,额外留16的边界宽 */
s32FontWidth = MeasureText.measureText({ textContent: strShow, fontSize: inFontSize.toString() }) + 16;
/* 如果当前文本列宽度大于当前保存的宽度,就要重新调整宽度,取最宽的地方 */
if (indexColumn < this.cloumnFontWidth.length && s32FontWidth > this.cloumnFontWidth[indexColumn]) {
this.cloumnFontWidth[indexColumn] = s32FontWidth;
s32TotalWidth = 0;
columnsTemplateStr = '';
/* 总的宽度(this.gridWidth)需要重新统计并生效,总的列比例(this.columnsTemplateStr)也要重新统计并生效 */
for (s32I = 0; s32I < this.cloumnFontWidth.length; s32I++) {
s32TotalWidth += this.cloumnFontWidth[s32I];
columnsTemplateStr += this.cloumnFontWidth[s32I].toString() + 'fr' + ' ';
}
this.gridWidth = s32TotalWidth.toString() + 'px';
this.columnsTemplateStr = columnsTemplateStr;
}
return strShow;
}
@Builder
showGridItem(item: EqResultNode, indexRow:number) {
/* 一行中的每一列处理,总共5列,第一列字体大小16,其他列字体大小18,左对齐 */
ForEach([0, 1, 2, 3, 4], (itemNumberColumn: number, index: number) => {
GridItem() {
Text(this.showGridItemFormShowStr(item, indexRow, itemNumberColumn, itemNumberColumn == 0 ? 16 : 18))
.fontSize(itemNumberColumn == 0 ? 16 : 18).maxLines(1).textAlign(TextAlign.Start)
}.border({
width: 1,
color: Color.Green,
style: BorderStyle.Solid
}).padding(0)
}, (itemNumberColumn: number, index: number) => {
return (itemNumberColumn * index).toString();
})
}
calShowGridTitleShowTextHeight(strItem: string, s32FontSize:number, s32IndexColumn: number) {
/* 计算标题栏宽和高 */
let sizeOptions = MeasureText.measureTextSize({ textContent: strItem, fontSize: s32FontSize.toString() });
let s32FontHeight = sizeOptions.height;
let s32FontWidth = sizeOptions.width;
let s32TotalWidth:number = 0;
let s32I:number = 0;
let columnsTemplateStr:string = '';
if (s32FontHeight != undefined && (typeof s32FontHeight == 'number')) {
s32FontHeight += 6;
if (this.s32TitleHeight < s32FontHeight) {
/* 标题栏每一列,高度取最大值 */
this.s32TitleHeight = s32FontHeight;
}
}
/* 如果当前标题栏文本列宽度大于当前保存的宽度,就要重新调整宽度,取最宽的地方 */
if (s32IndexColumn < this.cloumnFontWidth.length && (typeof s32FontWidth == 'number') && s32FontWidth > this.cloumnFontWidth[s32IndexColumn]) {
this.cloumnFontWidth[s32IndexColumn] = s32FontWidth;
s32TotalWidth = 0;
columnsTemplateStr = '';
/* 总的宽度(this.gridWidth)需要重新统计并生效,总的列比例(this.columnsTemplateStr)也要重新统计并生效 */
for (s32I = 0; s32I < this.cloumnFontWidth.length; s32I++) {
s32TotalWidth += this.cloumnFontWidth[s32I];
columnsTemplateStr += this.cloumnFontWidth[s32I].toString() + 'fr' + ' ';
}
this.gridWidth = s32TotalWidth.toString() + 'px';
this.columnsTemplateStr = columnsTemplateStr;
}
return strItem;
}
@Builder
showGridTitle(strItem: string, s32FontSize:number, s32IndexColumn: number) {
/* 显示标题栏中的一列 */
GridItem() {
Text(this.calShowGridTitleShowTextHeight(strItem, s32FontSize, s32IndexColumn))
.fontSize(s32FontSize)
}.border({
width: 1,
color: Color.Grey,
style: BorderStyle.Solid
})
}
build() {
Scroll() {
Column() {
/* 标题5列 */
Grid() {
this.showGridTitle('期次', 16, 0)
this.showGridTitle('本息', 16, 1)
this.showGridTitle('利息', 16, 2)
this.showGridTitle('本金', 16, 3)
this.showGridTitle('余额', 16, 4)
}
.border({
width: 1,
color: Color.Gray,
style: BorderStyle.Solid
})
.width(this.gridWidth)
.height(this.s32TitleHeight.toString() + 'px')
.columnsTemplate(this.columnsTemplateStr)
/* 正文内容,即具体房贷期数,30年就是360期,5列360行 */
Grid() {
ForEach(this.listResult, (item: EqResultNode, index: number) => {
this.showGridItem(item, index)
}, (item: EqResultNode, index: number) => {
return item.s32YearMonth.toString();
})
}
.height('auto')
.width(this.gridWidth)
.columnsTemplate(this.columnsTemplateStr)
.scrollBar(BarState.On)
.columnsGap(2)
.margin({bottom:(this.s32TitleHeight + 5).toString() + 'px'}) /* 没有这一行底部显示不全 */
.border({
width: 1,
color: Color.Gray,
style: BorderStyle.Solid
})
}.height('100%').justifyContent(FlexAlign.Center)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.On)
.width('100%')
}
}
功能代码,无需过度关注,仅让感兴趣的能运行通过
export class EqResultNode {
s32YearMonth:number = 0;
dlMonthRate:number = 0.0;
lInterest:number = 0;
lPrincipal:number = 0;
lRemindAmount:number = 0;
}
import { hilog } from "@kit.PerformanceAnalysisKit";
export default class IntegerFormat {
public static DoubleToLongRoundOff(dlIn:number) {
return Math.round(dlIn);
}
public static ShowMoney(s32Money:number) {
return Math.floor(s32Money / 100).toString() + '.' + Math.floor(s32Money % 100).toString().padStart(2, '0')
}
public static ArabicNumIntegerToChinese(s64ArabicNumInteger:number) {
let s32I:number = 0;
let s64Tmp:number = 0;
let strOut:string = '';
let s32TmpE:number = 0;
let bRemind0:boolean = false;
if (0 == s64ArabicNumInteger) {
return '零';
}
s64Tmp = 1;
for (s32I = 0; s32I < 20; s32I++) {
s64Tmp = s64Tmp * 10;
if (s64ArabicNumInteger < s64Tmp) {
s64Tmp = s64Tmp / 10;
break;
}
}
if (s32I >= 20) {
return '';
}
if (s32I > 8) {
strOut = IntegerFormat.ArabicNumIntegerToChinese(Math.floor(s64ArabicNumInteger / 100000000)) + '亿';
s64ArabicNumInteger = s64ArabicNumInteger % 100000000;
}
while (s32I >= 0) {
s32TmpE = Math.floor(s64ArabicNumInteger / s64Tmp) % 10;
if (0 == s32TmpE) {
bRemind0 = true;
} else {
if (bRemind0) {
strOut += IntegerFormat.ArabicSingleNumIntegerToChinese(0);
}
bRemind0 = false;
strOut += IntegerFormat.ArabicSingleNumIntegerToChinese(s32TmpE);
}
if (0 != s32TmpE) {
switch (s32I % 4) {
case 0:
break;
case 1:
strOut += '十';
break;
case 2:
strOut += '百';
break;
case 3:
strOut += '千';
break;
}
}
s64Tmp = s64Tmp / 10;
if (s32I == 4) {
strOut += '万';
}
s32I--;
}
return strOut;
}
public static AfterDecimalPointChineseShow(dlDecimal:number) {
let s32AfterDecimalInt:number = Math.round(dlDecimal * 100) % 100;
let s32TmpInt:number = 0;
let strOut:string = '';
if (0 == s32AfterDecimalInt) {
return strOut;
}
s32TmpInt = Math.floor(s32AfterDecimalInt / 10);
if (0 != s32TmpInt) {
strOut += (IntegerFormat.ArabicSingleNumIntegerToChinese(s32TmpInt) + '角');
}
s32TmpInt = s32AfterDecimalInt % 10;
if (0 != s32TmpInt) {
strOut += (IntegerFormat.ArabicSingleNumIntegerToChinese(s32TmpInt) + '分');
}
return strOut;
}
private static ArabicSingleNumIntegerToChinese(s32SingleNum:number) {
switch (s32SingleNum) {
case 0:
return '零';
case 1:
return '一';
case 2:
return '二';
case 3:
return '三';
case 4:
return '四';
case 5:
return '五';
case 6:
return '六';
case 7:
return '七';
case 8:
return '八';
case 9:
return '九';
default :
return '';
}
return '';
}
}
运行效果:
因为最多只能5M,比较短,注意看即能上下滑动,也能左右滑动,标题栏同步左右滚动
核心点分析:
1、文本显示宽度自适应
每一个显示的方格GridItem,包括标题栏,计算文本的宽度,当该列宽度大于记录的列宽度,则保存下来。
然后重新计算总的列宽度即Grid宽度,重新计算列的比例。列的宽度我试了很多方法不能直接设置宽度值,只能设置每一列的宽度比例。
2、超出屏幕宽度,水平滚动
Grid默认支持垂直滚动,不支持水平滚动,所以水平滚动要单独处理。
把两个Grid,一个标题Grid一个内容Grid,用Column包裹起来,再在外面用水平Scroll包裹。简化如下
build() {
Scroll() {
Column() {
Grid() {}
Grid() {}
}.height('100%').justifyContent(FlexAlign.Center)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.On)
.width('100%')
}
3、标题栏显示和内容列对齐,即宽度等宽水平滚动同步
从上面简化代码看到:
把两个Grid,一个标题Grid一个内容Grid,用Column包裹起来,再在外面用水平Scroll包裹。
4、不够屏幕宽居中显示
调试了很久,从简化代码看到:
Column() {
}.height('100%').justifyContent(FlexAlign.Center)
关键语句:justifyContent(FlexAlign.Center)
更多推荐

所有评论(0)