鸿蒙实战开发(HarmonyOS)实现自定义动效tab

打印 上一主题 下一主题

主题 1046|帖子 1046|积分 3142

  鸿蒙NEXT开发实战往期必看文章:
一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发!
“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零根本入门到醒目)
HarmonyOS NEXT应用开发案例实践总结合(持续更新......)
HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)

介绍

本示例介绍利用List、Text等组件,以及animateTo等接口实现自定义Tab效果
效果预览图



利用阐明
1.选中页签,字体放大加粗且后面有配景条,起到强调作用。
2.手势触摸tab内容滑动,配景条跟随手势一起滑动。抬手时,当tab内容滑动隔断不足一半时,会自动回弹,而当tab内容滑动隔断大于一半时,配景条则会移动到下一个页签。当配景条滑动到肯定隔断后开始滑动页签条,使配景条始终能够保持在可视范围内。
3.点击页签,可以举行页签切换。
4.滑动页签条,配景条也会随之一起滑动,然后滑动tab内容,页签条会滑动到原处,使配景条处于可视范围内,之后配景条开始跟随手势滑动。
5.动画承接,配景条滑动过程中,触摸屏幕,配景条动画制止,松开手势,配景条继续滑动
下载安装

1.模块oh-package.json5文件中引入依赖
  1. "dependencies": {
  2.   "@ohos-cases/custom_animation_tab": "har包地址"
  3. }
复制代码
2.ets文件import自定义视图实现Tab效果组件
  1. import {CustomAnimationTab} from '@ohos-cases/custom_animation_tab'
复制代码
快速利用

本节重要介绍了如何快速上手利用自定义视图实现Tab效果组件,包括构建Tab组件以及常见自定义参数的初始化。
1.构建Tab
在代码符合的位置利用CustomAnimationTab组件并传入对应的参数(animationAttribute必须设置,别的参数可以利用默认值),后续将分别介绍对应参数的初始化。
  1. /**
  2. * 构建自定义Tab
  3. * animationAttribute: 动效属性
  4. * tabsInfo: tab数据源
  5. * indicatorBarAttribute: 背景条属性
  6. * tabBarAttribute: 页签条属性
  7. * tabController: 自定义动效tab控制器
  8. * scroller: 页签条滚动控制器
  9. */
  10. CustomAnimationTab({
  11.   animationAttribute: this.animationAttribute,
  12.   tabsInfo: this.tabsInfo,
  13.   indicatorBarAttribute: this.indicatorBarAttribute,
  14.   tabBarAttribute: this.tabBarAttribute,
  15.   tabController: this.tabController,
  16.   scroller: this.scroller
  17. })
复制代码
2.动效属性初始化
动效属性基类为AnimationAttribute,其中封装了tab组件内部的动效属性。本示例中提供了开发自定义配景条颜色动效属性的代码。首先创建一个MyAnimationAttribute类,并继续AnimationAttribute基类,其中新增配景条颜色属性indicatorBarColor;之后创建MyAnimationAttribute状态变量对象animationAttribute,并将配景条颜色属性绑定到自定义配景条上,同时与button相干联,通过点击事件动态更改配景条颜色。(这里需要注意class对象属性级更新的精确利用)
  1. // 自定义动效属性,添加了背景条颜色变化
  2. @State animationAttribute: MyAnimationAttribute = new MyAnimationAttribute($r("app.color.custom_animation_tab_indicator_color"));
复制代码
  1. export class MyAnimationAttribute extends AnimationAttribute {
  2.   // 背景条颜色
  3.   indicatorBarColor: ResourceColor;
  4.   constructor(indicatorBarColor: ResourceColor) {
  5.     super();
  6.     this.indicatorBarColor = indicatorBarColor;
  7.   }
  8. }
复制代码
  1. @Builder
  2. indicatorBar($$: BaseInterface) {
  3.   Column()
  4.     .height($r("app.float.custom_animation_tab_indicator_height"))
  5.     .width($r("app.string.custom_animation_tab_one_hundred_percent"))
  6.     // 绑定自定义动效属性
  7.     .backgroundColor(this.animationAttribute.indicatorBarColor)
  8.     .borderRadius($r("app.float.custom_animation_tab_indicator_border_radius"))
  9. }
  10. // 更新自定义动效变量——背景条颜色
  11. Column() {
  12.   Button($r("app.string.custom_animation_tab_button_text"))
  13.     .height($r("app.string.custom_animation_tab_ninety_percent"))
  14.     .type(ButtonType.Capsule)
  15.     .onClick(() => {
  16.       if ((this.animationAttribute.indicatorBarColor as Resource).id ===
  17.       $r("app.color.custom_animation_tab_indicator_color").id) {
  18.         this.animationAttribute.indicatorBarColor = Color.Yellow;
  19.       } else if (this.animationAttribute.indicatorBarColor === Color.Yellow) {
  20.         this.animationAttribute.indicatorBarColor = $r("app.color.custom_animation_tab_indicator_color");
  21.       }
  22.     })
  23. }
  24. .justifyContent(FlexAlign.Center)
  25. .height($r("app.string.custom_animation_tab_ten_percent"))
  26. .width($r("app.string.custom_animation_tab_one_hundred_percent"))
复制代码
3.数据初始化
本小节重要介绍了如何初始化自定义Tab数据源。首先构建一个TabInfo数组,然后向其中传入对应的TabInfo对象,TabInfo对象重要需要传入三个属性——页签标题、tab页面内容视图以及页签组件。以base页面为例,首先创建一个@Builder函数,在该函数中填入struct组件,在struct组件中编写对应tab页面内容视图。然后,构建对应的页签样式tabBar,其中需要添加一个TabBarItemInterface类对象作为形参,其包括了一些须要属性,可以自定义样式修改,本示例中重要通过利用当前索引curIndex与页签索引index之间的比较来动态更改页签样式。
  1. // tab数据
  2. tabsInfo: TabInfo[] = [];
  3. this.tabsInfo = [
  4.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_BASE_TAB, wrapBuilder(baseBuilder), wrapBuilder(tabBar)),
  5.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_UI_TAB, wrapBuilder(uiBuilder), wrapBuilder(tabBar)),
  6.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_DYEFFECT_TAB, wrapBuilder(dyEffectBuilder), wrapBuilder(tabBar)),
  7.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_THIRTYPARTY_TAB, wrapBuilder(thirdPartyBuilder), wrapBuilder(tabBar)),
  8.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_NATIVE_TAB, wrapBuilder(nativeBuilder), wrapBuilder(tabBar)),
  9.   new TabInfo(CustomAnimationTabConfigure.DEFAULT_OTHER_TAB, wrapBuilder(otherBuilder), wrapBuilder(tabBar))
  10. ]
  11.   
  12. // baseBuilder页面
  13. import LazyDataSource from './LazyDataSource';
  14. import { SkeletonLayout } from './SkeletonLayout';
  15. @Builder
  16. export function baseBuilder() {
  17.   BasePage();
  18. }
  19. @Component
  20. struct BasePage {
  21.   @State data: LazyDataSource<string> = new LazyDataSource<string>();
  22.   aboutToAppear(): void {
  23.     for (let i = 0; i < 100; i++) {
  24.       this.data.pushData(`${i}`);
  25.     }
  26.   }
  27.   build() {
  28.     Column() {
  29.       List() {
  30.         LazyForEach(this.data, (data: string) => {
  31.           ListItem() {
  32.             SkeletonLayout({isMine: false})
  33.           }
  34.         })
  35.       }
  36.       .cachedCount(1)
  37.       .width("100%")
  38.       .height("100%")
  39.     }
  40.     .width("100%")
  41.     .height("100%")
  42.   }
  43. }
  44. // 页签样式
  45. @Builder
  46. function tabBar($$: TabBarItemInterface) {
  47.   Text($$.title)
  48.     .fontSize($$.curIndex === $$.index ? $r("app.float.custom_animation_tab_list_select_font_size") : $r("app.float.custom_animation_tab_list_unselect_font_size"))
  49.     .fontColor($r("app.color.custom_animation_tab_list_font_color"))
  50.     .fontWeight($$.curIndex === $$.index ? FontWeight.Bold : FontWeight.Medium)
  51.     .textAlign(TextAlign.Center)
  52. }
复制代码
4.配景条初始化
配景条可以通过IndicatorBarAttribute类举行设置,也可以利用已有的配景条设置(目前支持两种: IndicatorBarAttribute.BACKGROUNDBAR和IndicatorBarAttribute.THINSTRIP)。本示例重要介绍了构建IndicatorBarAttribute类举行配景条设置,其中传入了配景条组件indicatorBar ,配景条宽度模式设置为内边距模式,左右边距设为20,上下边距设为10,同时设置配景条最大偏移为CustomAnimationTabConfigure.INDICATOR_MAX_LEFT以及配景条宽度扩展比例为CustomAnimationTabConfigure.DEFAULT_INDICATOR_EXPAND。
  1. indicatorBarAttribute: IndicatorBarAttribute = new IndicatorBarAttribute(this.indicatorBar, SizeMode.Padding, 20, 10,
  2.   CustomAnimationTabConfigure.INDICATOR_MAX_LEFT, CustomAnimationTabConfigure.DEFAULT_INDICATOR_EXPAND);
  3. @Builder
  4. indicatorBar($$: BaseInterface) {
  5.   Column()
  6.     .height($r("app.string.custom_animation_tab_one_hundred_percent"))
  7.     .width($r("app.string.custom_animation_tab_one_hundred_percent"))
  8.     .backgroundColor(this.animationAttribute.indicatorBarColor)
  9.     .borderRadius($r("app.float.custom_animation_tab_indicator_border_radius"))
  10. }
复制代码
5.页签条初始化
页签条属性通过TabBarAttribute类举行设置。本例中重要设置了各个页签的宽度巨细以及页签条高度。
  1. tabBarAttribute: TabBarAttribute = new TabBarAttribute(CustomAnimationTabConfigure.LIST_ITEM_WIDTH, CustomAnimationTabConfigure.TABBAR_HEIGHT)
复制代码
6.tab及页签条控制器初始化
自定义Tab控制器以及页签条控制器分别通过CustomAnimationTabController和Scroller初始化,分别用于控制自定义Tab与页签条的举动。
  1. // tabController
  2. tabController: CustomAnimationTabController = new CustomAnimationTabController();
  3. // scroller
  4. scroller: Scroller = new Scroller();
复制代码
属性(接口)阐明

CustomAnimationTab组件属性
属性类型释义默认值animationAttributeAnimationAttribute封装了包括配景条长度、配景条高度以及配景条左边距在内的动效属性,使组件内部对应的属性可以动态变化undefinedtabsInfoTabInfo[]tab数据源-indicatorBarAttributeIndicatorBarAttribute配景条属性类,设置配景条组件,以及配景条相干的属性-tabBarAttributeAnimationAttribute页签条属性类,设置页签条相干的属性-tabControllerCustomAnimationControllertab控制器,用于控制tabs组件举行页签切换-scrollerScroller页签条控制器,可以控制页签条的滚动-animationDurationnumber页签切换时长,控制页签切换时配景条滑动以及tab页面之间切换的时长240msstartIndexnumber设置起始的页签索引0gestureAnimation(index: number, targetIndex: number, elementsInfo: [number, number][], ratio: number) => void设置在tab页面上手势滑动时配景条的跟手动效-autoAnimation(index: number, targetIndex: number, elementsInfo: [number, number][], ratio: number) => void设置在tab页面上离手后配景条的自动滑动动效-clickAnimation(index: number, targetIndex: number, indexInfo: Record<string, number>, targetIndexInfo: Record<string, number>, elementsInfo: [number, number][]) => void设置点击页签时配景条的滑动动效-getScrollInfo(center: number, width: number) => [number, number]获取页签对应的配景条左边距以及页签条偏移,通过该函数可以自行设置选中各个页签时配景条的左边距以及页签条的偏移情况- TabInfo类属性
属性类型释义默认值titlestring页签标题-contentbuilderWrappedBuilder<[]>tab页面内容视图-barBuilderWrappedBuilder<[TabBarItemInterface]>页签组件(没有设置,则利用内部默认设置)undefined IndicatorBarAttribute类属性
属性类型释义默认值indicatorBar(index: BaseInterface) => void自定义配景条组件-sizeModeSizeMode配景条宽度模式SizeMode.NormalinnerWidthnumber1. 尺寸模式为正常模式,表示配景条宽度,值为0时则与页签宽度保持划一;
2. 尺寸模式为内边距模式,表示配景条与页签项之间的左右边距0innerHeightnumber1. 尺寸模式为正常模式,表示配景条高度,值为0时则与页签高度保持划一;
2. 尺寸模式为内边距模式,表示配景条与页签项之间的上下边距0maxIndicatorBarLeftnumber配景条最大偏移(<0: 无上限, >=0: innerMaxIndicatorBarLeft),设置配景条最大的滑动隔断,超过该隔断后除非页签条滑动到了底部,否则滑动页签条,配景条不再滑动-1indicatorExpandnumber配景条宽度扩展比例,设置配景条在滑动过程中宽度扩展的比例1barAlignVerticalAlign配景条垂直布局,设置配景条相对于页签的位置VerticalAlign.Center TabBarAttribute类属性
属性类型释义默认值barItemWidthLength各个页签项的宽度(没有设置且尺寸模式为正常模式时,与页签同宽;没有设置且尺寸模式为内边距模式时,与配景条同宽)undefinedbarHeightLength页签条高度(没有设置且尺寸模式为正常模式时,与首个页签同高;没有设置且尺寸模式为内边距模式时,与配景条同高)undefinedscrollableboolean是否可以滚动页签条(false则所有页签等分屏幕宽度,barItemWidth失效)truebarEdgeEffectEdgeEffect页签条边缘滑动效果,支持弹簧效果和阴影效果EdgeEffect.SpringbarVerticalBarPosition页签条位置,处于tab内容的上方或下方BarPosition.StartbarBackgroundColorResourceColor页签条配景颜色Color.Transparent BaseInterface属性
属性类型释义默认值curIndexnumber当前选中的页签索引- TabBarItemInterface属性
属性类型释义默认值curIndexnumber当前选中的页签索引-indexnumber页签本身索引-titlestring页签标题- SizeMode属性
属性类型释义默认值Normal-尺度宽度模式,配景条尺寸通过配景条宽高属性显示设置-Padding-内边距模式,配景条尺寸通过页签上下边距隐性设置- 实现思绪

本案例的功能实现重要可以分为两个部门:第一个是点击页签的切换,第二个是滑动tab的切换。在后续两小节将对以上两个部门举行详细介绍。然后,我们展示了一些重要的变量名及其含义。


  • maxListOffset:页签条最大偏移隔断
  • maxIndicatorBarLeft: 配景条最大偏移隔断
  • AnimationAttribute.left:配景条位置
1.核心函数getScrollInfo

由于我们将页签动画效果分为两种不同类型的滑动,因此需要实现一个函数以分别获取每个页签对应的配景条位置以及页签条滑动偏移。
1.1 配景条最大滑动隔断以及页签条最大滑动隔断
首先,我们需要了解两个概念:配景条最大滑动隔断以及页签条最大滑动隔断,如下图所示。
(1)配景条最大偏移隔断:配景条滑动到该处时不再向后滑动,此时页签条接管滑动。
(2)页签条最大偏移隔断:当页签条接管滑动以后,当滑动到末尾时,无法向后滑动,此时配景条再次接管滑动。 

1.2 三个阶段
从上面的两个概念,我们可以看出滑动重要可以切分为三个阶段:1)配景条初始滑动阶段;2)页签条滑动阶段;3)配景条再次滑动阶段。
1.3 代码实现
通过以上两个小节的介绍,我们可以尝试实当代码。具体代码如下所示:
  1. /**
  2. * 获取页签对应的背景条位置以及页签条偏移
  3. * @param center - 页签中心点
  4. * @param width - 页签条宽度
  5. * @returns: [背景条左端位置, 页签条偏移]
  6. */
  7. @Param getScrollInfo: (center: number, width: number) => [number, number] =
  8.   (center: number, width: number): [number, number] => {
  9.     // 获取背景条位置
  10.     let indicatorLeft: number = center - this.indicatorBarWith / 2;
  11.     // TODO: 知识点: 当背景条位置大于默认的背景条最大位置时,选取背景条最大位置作为背景条实际位置
  12.     let finalIndicatorLeft: number = this.maxIndicatorBarLeft >= 0 ? Math.min(indicatorLeft, this.maxIndicatorBarLeft) : indicatorLeft;
  13.     // TODO: 知识点: 背景条产生的多余距离作为页签条滑动距离
  14.     let listOffset: number = indicatorLeft - finalIndicatorLeft;
  15.     // TODO: 知识点: 当页签条偏移大于页签条可偏移量,选取页签条可偏移量作为页签条实际偏移
  16.     let finalListOffset: number = Math.min(listOffset, this.maxListOffset);
  17.     // TODO: 知识点: 页签条多余的偏移作为背景条后续的滑动距离
  18.     finalIndicatorLeft += listOffset - finalListOffset;
  19.     return [finalIndicatorLeft, finalListOffset];
  20.   };
复制代码
具体思绪:首先我们可以根据页签的位置信息获取对应配景条的初始位置。1)第一阶段:配景条初始位置就是配景条的实际位置;2)第二阶段:当配景条偏移大于配景条最大偏移隔断时,进入第二阶段。这时候后续多余的配景条偏移需要作为页签条偏移,以实现页签条移动;3)第三阶段:当页签条偏移大于页签条最大偏移量时,进入第三阶段。此时多余的页签条偏移会作为配景条的偏移,使配景条继续向后滑动。
2.点击页签的切换



  • 首先在onChange回调中实现对应的动画效果,当事件为点击事件并且需要举行页签切换时才进入到对应的动画效果实现,其中首先通过获取index页签的中心位置计算配景条位置,以实现配景条移动到当前页签位置。然后,通过elementsInfo数组获取index页签对应的页签条偏移,从而对页签条举行滑动。而配景条的滑动则通过页签条的滑动回调函数onDidScroll来举行。
  1. // tab
  2. Swiper(this.swiperController) {
  3.   // 布局实现
  4. }
  5. .onChange((index: number) => {
  6.   // 点击事件且发生页签切换
  7.   if (this.listTouchState === 1 && index !== this.curIndex) {
  8.     let indexInfo: Record<string, number> = this.getElementInfo(this.curIndex);
  9.     let targetIndexInfo: Record<string, number> = this.getElementInfo(index);
  10.     this.clickAnimation(this.curIndex, index, indexInfo, targetIndexInfo, this.elementsInfo);
  11.   }
  12.   this.curIndex = index;
  13.   console.log(`curIndex: ${this.curIndex}`)
  14. })
  15. clickAnimation: (targetIndex: number, targetIndexInfo: Record<string, number>, elementsInfo: IndicatorAnimationInfo[]) => void =
  16.   (targetIndex: number, targetIndexInfo: Record<string, number>, elementsInfo: IndicatorAnimationInfo[]): void => {
  17.     // 根据targetIndex页签当前位置获取对应的背景条位置
  18.     this.animationAttribute.left = targetIndexInfo.center - this.elementsInfo[targetIndex].width / 2;
  19.     this.animationAttribute.indicatorBarWidth = this.elementsInfo[targetIndex].width;
  20.     this.animationAttribute.indicatorBarHeight = this.elementsInfo[targetIndex].height;
  21.     this.scroller!.scrollTo({xOffset: elementsInfo[targetIndex].offset, yOffset: 0, animation: {duration: this.animationDuration, curve: Curve.Linear}});
  22.   };
复制代码


  • 在页签点击事件中触发页签切换事件,后续就会触发tab的onChange事件实现切换动画。
  1. // 页签点击事件
  2. ListItem() {
  3.   // 布局实现
  4. }
  5. .onClick(() => {
  6.   this.listTouchState = 1;
  7.   this.tabController.changeIndex(index);
  8. })
复制代码
2.滑动Tab的切换

滑动页签切换重要分为两个部门:一个是配景条的滑动,一个是页签条的滑动。
2.1 手势跟踪
  1. Swiper(this.swiperController) {
  2.   // 布局实现
  3. }
  4. .onGestureSwipe((index: number, event: TabsAnimationEvent) => {
  5.   this.listTouchState = 0;
  6.   let curOffset: number = event.currentOffset;
  7.   let targetIndex: number = index;
  8.   this.isReachBorder = false;
  9.   // tab组件到达边界使背景条和页签条跳转到终点位置
  10.   // TODO: 知识点: 这里不能判断到边界直接退出,因为onGestureSwipe每一帧触发回调,当手势滑动较快,上一帧背景条没有到达边界
  11.   // TODO(接上): 知识点: 下一帧content超出边界,这时候背景条没有更新,退出将导致背景条停滞在上一帧位置无法更新。
  12.   if ((index === 0 && curOffset > 0) ||
  13.     (index === this.innerBarData.length - 1 && curOffset < 0)) {
  14.     this.isReachBorder = true;
  15.     curOffset = 0;
  16.   }
  17.   let ratio: number = Math.abs(curOffset / this.tabsWidth); // tab滑动比例
  18.   if (curOffset < 0) { // tab右滑
  19.     targetIndex = index + 1;
  20.   } else if (curOffset > 0) { // tab左滑
  21.     targetIndex = index - 1;
  22.   }
  23.   // 获取背景条位置及页签条偏移
  24.   // 获取背景条位置及页签条偏移
  25.   this.gestureAnimation(index, targetIndex, this.elementsInfo, ratio);
  26. })
  27. /**
  28. * 手势滑动动效
  29. * @param index - 起始页签索引
  30. * @param targetIndex - 目标页签索引
  31. * @param elementsInfo - 页签信息[背景条左端位置, 页签条偏移]
  32. * @param ratio - 当前手势滑动比例
  33. * @returns
  34. */
  35. gestureAnimation: (index: number, targetIndex: number, elementsInfo: IndicatorAnimationInfo[], ratio: number) => void =
  36.   (index: number, targetIndex: number, elementsInfo: IndicatorAnimationInfo[], ratio: number): void => {
  37.     this.animationAttribute.left = elementsInfo[index].left + (elementsInfo[targetIndex].left - elementsInfo[index].left) * ratio;
  38.     this.scroller!.scrollTo({xOffset: elementsInfo[index].offset + (elementsInfo[targetIndex].offset - elementsInfo[index].offset) * ratio, yOffset: 0});
  39.     let indicatorSize: [number, number] = this.getIndicatorSize(ratio, index, targetIndex);
  40.     this.animationAttribute.indicatorBarWidth = indicatorSize[0];
  41.     this.animationAttribute.indicatorBarHeight = indicatorSize[1];
  42.   };
复制代码
具体思绪: 手势跟踪滑动重要存在两种情况:1)配景条到达边界;2)配景条未到达边界。首先判断tab是否滑动到边界,若滑动到边界,则目的页签等于当前页签。否则,则根据当前的偏移情况来判断目的页签相对于当前页签的位置。然后,分别获取当前页签以及目的页签对应的配景条位置以及页签条偏移作为配景条和页签条的起始状态和最终状态。之后,可以通过计算tab滑动比例,获取当前配景条位置以及页签条偏移,公式如下所示: 

2.2 动画效果
  1. Swiper(this.swiperController) {
  2.   // 布局实现
  3. }
  4. .onContentDidScroll((selectedIndex: number, index: number, position: number, mainAxisLength: number) => {
  5.   // 动画启动,选取当前index索引页签的属性来执行背景条和页签条滑动
  6.   if (this.isAnimationStart && index === this.innerCurrnetIndex) {
  7.     // 使用选中页签相对于Swiper主轴起始位置的移动比例判断滑动的目标页签targetIndex的位置
  8.     let targetIndex: number = position < 0 ? index + 1 : index - 1;
  9.     if (targetIndex >= this.innerBarData.length || targetIndex < 0) {
  10.       console.warn(`Error: targetIndex exceeds the limit range:
  11.             selectedIndex: ${selectedIndex}, curIndex: ${this.innerCurrnetIndex}, index: ${index},
  12.             targetIndex: ${targetIndex}, position: ${position}, mainAxisLength: ${mainAxisLength}`);
  13.       return;
  14.     }
  15.     let ratio: number = Math.abs(position);
  16.     // 通过页签比例计算当前页签条和背景条的位置
  17.     this.autoAnimation(index, targetIndex, this.elementsInfo, ratio);
  18.   }
  19. })
  20. .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
  21.   if (this.isReachBorder) { // 若tab到达边界,则不继续执行动画
  22.     return;
  23.   }
  24.   this.isAnimationStart = true;
  25.   this.listTouchState = 0;
  26. })
  27. .onAnimationEnd(() => {
  28.   this.isAnimationStart = false;
  29. })
  30. /**
  31. * 自动滑动动效
  32. * @param index - 起始页签索引
  33. * @param targetIndex - 目标页签索引
  34. * @param elementsInfo - 页签动效信息[背景条左端位置, 页签条偏移]
  35. * @param ratio - 当前tab滑动比例
  36. * @returns
  37. */
  38. autoAnimation: (index: number, targetIndex: number, elementsInfo: IndicatorAnimationInfo[], ratio: number) => void =
  39.   (index: number, targetIndex: number, elementsInfo: IndicatorAnimationInfo[], ratio: number): void => {
  40.     this.animationAttribute.left = elementsInfo[index].left + (elementsInfo[targetIndex].left - elementsInfo[index].left) * ratio;
  41.     this.scroller!.scrollTo({
  42.       xOffset: elementsInfo[index].offset + (elementsInfo[targetIndex].offset - elementsInfo[index].offset) * ratio,
  43.       yOffset: 0
  44.     });
  45.     let indicatorSize: [number, number] = this.getIndicatorSize(ratio, index, targetIndex);
  46.     this.animationAttribute.indicatorBarWidth = indicatorSize[0];
  47.     this.animationAttribute.indicatorBarHeight = indicatorSize[1];
  48.   };
复制代码
具体思绪:首先在动画开始时,我们在onAnimationStart回调中只举行动画开始状态的改变(i.e. this.isAnimationStart = true)。然后,在onContentDidScroll回调中举行绘制动画。具体来说,在每一次回调onContentDidScroll接口时通过起始页签index、目的页签targetIndex以及滑动比例来判断当前配景条位置以及页签条的偏移,如公式(1)所示。 因此,动画函数中最重要的就是判断index、targetIndex以及滑动比例。由于页签条的滑动等价于配景条滑动,因此我们只需要判断配景条的滑动情况就可以覆盖所有情况。如下图所示,这里重要存在以下三种情况的判断:1)配景条未回弹且滑动比例小于0.5;2)配景条未回弹且滑动比例大于等于0.5;3)配景条回弹。


  • 配景条未回弹且滑动比例小于0.5。这时候起始页签index应该等于curIndex,而目的页签targetIndex则可以根据滑动比例正负判断index+1(index-1)。当tab不断向左(右)滑动时,index页签滑动比例不断增加,配景条也不断向右(左)滑动。 

  • 配景条回弹。这时候起始页签应该等于curIndex,而目的页签targetIndex则可以根据滑动比例正负判断index+1(index-1)。当tab回弹时,index页签滑动比例不断减少,配景条也不断向左(右)滑动,直至回弹到原位置。 

  • 配景条未回弹且滑动比例大于等于0.5。这时候目的页签应该等于curIndex,起始页签index应该则可以根据滑动比例正负判断targetIndex+1(targetIndex-1)。但是,仔细观察我们可以发现,实在这种情况与配景条回弹情况根本划一。可以将其看作是黄色页签开始向左滑动,也可以将其看作是绿色页签开始举行回弹。因此,我们可以将其转化为绿色页签回弹,如后续第二张图所示。这时候起始页签应该等于curIndex,而目的页签targetIndex则可以根据滑动比例正负判断index+1(index-1)。当index页签内容回弹时,tab滑动比例不断减少,配景条也不断向右(左)滑动,直至回弹到原位置。 

     

高性能知识点

本示例利用了LazyForEach举行数据懒加载,LazyForEach懒加载可以通过设置cachedCount属性来指定缓存数量,同时搭配组件复用能力以达到性能最优效果。
工程布局&模块类型

  1. customAnimationTabs                          // har类型
  2. |---common
  3. |   |---CommonConstants.ets                  // 内置常量定义
  4. |---model
  5. |   |---AnimationAttribute.ets               // 动效属性
  6. |   |---BaseInterface.test                   // 基础信息接口
  7. |   |---ComponentFactory.ets                 // 组件工厂
  8. |   |---CustomAniamtionTabController.ets     // 自定义tab控制器
  9. |   |---IndicatorBarAttribute.ets            // 背景条属性
  10. |   |---TabBarAttribute.ets                  // 页签条属性
  11. |   |---TabBarItemInterface.ets              // 页签信息接口
  12. |   |---TabInfo.ets                          // tab项信息
  13. |---utils
  14. |   |---CustomAnimationTab.ets               // customAnimationTab组件
  15. |---view
  16. |   |---BasePage.ets                         // tab页面内容及页签
  17. |   |---CustomAnimationTabConfigure.ets      // 用户配置
  18. |   |---CustomAnimationTabView.ets           // 样例页面
  19. |   |---DyEffectPage.ets                     // tab页面内容及页签
  20. |   |---LazyDataSource.ets                   // 懒加载数据
  21. |   |---NativePage.ets                       // tab页面内容及页签
  22. |   |---OtherPage.ets                        // tab页面内容及页签
  23. |   |---SkeletonLayout.ets                   // 骨架页面
  24. |   |---ThirdPartyPage.ets                   // tab页面内容及页签
  25. |   |---UIPage.ets                           // tab页面内容及页签
  26. |---FeatureComponent.ets                     // AppRouter入口文件
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表