api18
效果:
代码:
import { CommonConstants, fpConversion, NavigationBarView, PopViewUtil, primaryColor, vpConversion } from 'common'; import { BuilderNameConstants, RouterModule } from 'routers'; import { componentUtils } from '@kit.ArkUI'; import { DietModel, DietModelPetdietData, } from '../model/DietModel'; import { AppUtil } from '@pura/harmony-utils'; import { util } from '@kit.ArkTS'; import { DataAdapter } from '../model/DataAdapter'; import { DietDialog } from '../components/DietDialog'; @Extend(Text) function dietText() { .fontSize(fpConversion(13)) .height(vpConversion(36)) .width(vpConversion(86)) .textAlign(TextAlign.Center) } export enum DietTabType { ok = 0, possible, impossible } export const TabItemData: TabItem[] = [ { id: DietTabType.ok, name: '猫咪可以吃', data: [] }, { id: DietTabType.possible, name: '猫咪谨慎吃', data: [] }, { id: DietTabType.impossible, name: '猫咪不能吃', data: [] } ]; export interface TabItem { id: number name: string | Resource data: DietModelPetdietData[] } /** * 宠物饮食 */ @ComponentV2 export struct DietView { @Local focusIndex: number = 0; //当前选中tab @Local indicatorLeftMargin: number = 10; //指示条左边距(控制位置) private controller: TabsController = new TabsController(); //控制tabs切换 @Local tabArray: Array<TabItem> = []; //数据源 private tabsWidth: number = 0; // tabs宽度 private tabWidth: number = 0; // 单tab宽度 private iteration: number = 1; private swipeRatio: number = 0.5; private leftPadding: number = 20; //居中给的左边距 adapter: DataAdapter<DietModelPetdietData> = new DataAdapter<DietModelPetdietData>(); @Local source: DietModelPetdietData[] = [] aboutToAppear(): void { // this.tabArray = TabItemData; this.indicatorLeftMargin = this.leftPadding + (this.focusIndex * this.tabWidth); this.initTabContext() } async initTabContext() { const catOk = await AppUtil.getContext().resourceManager.getRawFileContent('CanEatData.json') const catPossible = await AppUtil.getContext().resourceManager.getRawFileContent('CarefulToEatData.json') const catImpossible = await AppUtil.getContext().resourceManager.getRawFileContent('CanNotEatData.json') let textDecode = util.TextDecoder.create('utf-8') const okResult = JSON.parse(textDecode.decodeToString(catOk)) as DietModel const possibleResult = JSON.parse(textDecode.decodeToString(catPossible)) as DietModel const impossibleResult = JSON.parse(textDecode.decodeToString(catImpossible)) as DietModel const source = TabItemData source[DietTabType.ok].data = okResult.Petdiet.data source[DietTabType.possible].data = possibleResult.Petdiet.data source[DietTabType.impossible].data = impossibleResult.Petdiet.data this.tabArray = source } @Builder Tab(tabName: string | Resource, tabIndex: number) { Row() { Text(tabName) .dietText() .id(tabIndex.toString()) .fontColor(tabIndex === this.focusIndex ? Color.White : $r('app.color.color_333333')) .onAreaChange((_oldValue: Area, newValue: Area) => { if (this.focusIndex === tabIndex && (this.indicatorLeftMargin === 0)) { if (newValue.position.x !== undefined) { let positionX = Number.parseFloat(newValue.position.x.toString()) this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX; let width = Number.parseFloat(newValue.width.toString()); this.tabWidth = Number.isNaN(width) ? 0 : width; } } }) } .justifyContent(FlexAlign.Center) .height(vpConversion(36)) .onClick(() => { this.focusIndex = tabIndex; this.controller.changeIndex(tabIndex); }) } @Builder TabContent(item: DietModelPetdietData) { Column({ space: vpConversion(8) }) { Row() { Image(item.img) .width(vpConversion(98)) .height(vpConversion(80)) } .width(CommonConstants.FULL_SCREEN) .justifyContent(FlexAlign.Center) Divider() .backgroundColor($r('app.color.color_ECECEC')) .width(CommonConstants.FULL_SCREEN) .height(vpConversion(1)) Text(item.name) .width(CommonConstants.FULL_SCREEN) .fontColor($r('app.color.color_323334')) .fontSize(fpConversion(16)) .fontWeight(FontWeight.Bold) Divider() .backgroundColor(primaryColor()) .width(vpConversion(26)) .height(vpConversion(3)) .borderRadius(vpConversion(10)) Text(item.content) .fontSize(fpConversion(13)) .width(CommonConstants.FULL_SCREEN) .maxLines(3) .ellipsisMode(EllipsisMode.END) .fontColor($r('app.color.color_A1A1A1')) } .alignItems(HorizontalAlign.Start) .backgroundColor(Color.White) .borderRadius(vpConversion(10)) .width(CommonConstants.FULL_SCREEN) .height(vpConversion(200)) .justifyContent(FlexAlign.Start) .padding({ left: vpConversion(10), right: vpConversion(10) }) .onClick(() => { PopViewUtil.showPopView<DietModelPetdietData>(wrapBuilder(DietDialog), item, { alignment: DialogAlignment.Center, autoCancel: true, isModal: true, }) }) } build() { NavigationBarView({ barTitle: $r('app.string.diet') }) { Column() { Stack({ alignContent: Alignment.TopStart }) { Row() .backgroundColor($r('app.color.color_E6E6E6')) .height(vpConversion(36)) .borderRadius(vpConversion(6)) .width('calc(100% - 40vp)') .margin({ left: vpConversion(20) }) Column() .height(vpConversion(36)) .width(vpConversion(86)) .margin({ left: this.indicatorLeftMargin }) .backgroundColor(primaryColor()) .borderRadius(vpConversion(6)) Row() { ForEach(this.tabArray, (item: TabItem, index: number) => { this.Tab(item.name, index) }, (item: TabItem, index: number) => JSON.stringify(item) + index) } .padding({ left: 20, right: 20 }) .alignItems(VerticalAlign.Center) .justifyContent(FlexAlign.SpaceBetween) .borderRadius(vpConversion(6)) .height(vpConversion(36)) .width(CommonConstants.FULL_SCREEN) } .width(CommonConstants.FULL_SCREEN) Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { ForEach(this.tabArray, (item: TabItem) => { TabContent() { Grid() { ForEach(item.data, (data: DietModelPetdietData) => { GridItem() { this.TabContent(data) } }) } .padding({ left: vpConversion(10), right: vpConversion(10) }) .columnsTemplate('1fr 1fr') .columnsGap(vpConversion(20)) .rowsGap(vpConversion(20)) .width(CommonConstants.FULL_SCREEN) .height(CommonConstants.FULL_SCREEN) .edgeEffect(EdgeEffect.None) } }, (item: TabItem, index: number) => JSON.stringify(item) + index) } .margin({ top: vpConversion(20) }) .onAreaChange((_oldValue: Area, newValue: Area) => { let width = Number.parseFloat(newValue.width.toString()); this.tabsWidth = Number.isNaN(width) ? 0 : width; }) .width(CommonConstants.FULL_SCREEN) .barHeight(0) .animationDuration(300) .onAnimationStart((index: number, targetIndex: number) => { this.focusIndex = targetIndex; let targetIndexInfo = this.getTextInfo(targetIndex); this.startAnimateTo(300, targetIndexInfo.left); }) .onAnimationEnd((index: number, event: TabsAnimationEvent) => { let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event); this.startAnimateTo(0, currentIndicatorInfo.left); }) .onGestureSwipe((index: number, event: TabsAnimationEvent) => { let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event); this.focusIndex = currentIndicatorInfo.index; this.indicatorLeftMargin = currentIndicatorInfo.left; this.tabWidth = currentIndicatorInfo.width; }) } .padding({ top: vpConversion(20) }) .backgroundColor($r('app.color.color_F7F7F7')) .width(CommonConstants.FULL_SCREEN) } } private getTextInfo(index: number): Record<string, number> { let modePosition: componentUtils.ComponentInfo = this.getUIContext().getComponentUtils().getRectangleById(index.toString()); return { 'left': this.getUIContext().px2vp(modePosition.windowOffset.x), 'width': this.getUIContext().px2vp(modePosition.size.width) } } private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> { let nextIndex = index; if (index > 0 && event.currentOffset > 0) { nextIndex--; } else if (index < this.tabArray.length - 1 && event.currentOffset < 0) { nextIndex++; } let indexInfo = this.getTextInfo(index); let nextIndexInfo = this.getTextInfo(nextIndex); let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth); let currentIndex = swipeRatio > this.swipeRatio ? nextIndex : index; let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * swipeRatio; let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatio; return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }; } private startAnimateTo(duration: number, leftMargin: number) { this.getUIContext().animateTo({ duration: duration, curve: Curve.Linear, iterations: this.iteration, playMode: PlayMode.Normal, }, () => { this.indicatorLeftMargin = leftMargin; }) } } // --- 路由 if (!RouterModule.getBuilder(BuilderNameConstants.DIET_PAGE)) { let builder: WrappedBuilder<[object]> = wrapBuilder(EncyclopediaDetailBuilder) RouterModule.registerBuilder(BuilderNameConstants.DIET_PAGE, builder) } @Builder export function EncyclopediaDetailBuilder(params: object) { DietView() }原理:
动态改变左边距