harmony-仿飞书导航-移动背景

harmony-仿飞书导航-移动背景 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: ArrayTabItem []; //数据源 private tabsWidth: number 0; // tabs宽度 private tabWidth: number 0; // 单tab宽度 private iteration: number 1; private swipeRatio: number 0.5; private leftPadding: number 20; //居中给的左边距 adapter: DataAdapterDietModelPetdietData new DataAdapterDietModelPetdietData(); 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.showPopViewDietModelPetdietData(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): Recordstring, 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): Recordstring, 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() }原理动态改变左边距