HarmonyOS 鸿蒙Next swiper的页面高度不同时的滑动效果

发布于 1周前 作者 vueper 来自 鸿蒙OS

HarmonyOS 鸿蒙Next swiper的页面高度不同时的滑动效果

swiper的每个页面高度不同,随子组件内容自动变化,高度不可预知,在swiper左右滑动时,如何使下部的组件位置能平滑的移动,而不是突变。看了HarmonyOS NEXT应用开发案例里的Swiper高度可变化效果实现案例,实现的前提是它的页面高度是已知的,我的场景是,每个页面高度不可知的情况下如果平滑移动。

6 回复

可以参考下gitee目前的现成项目: Swiper高度可变化效果实现

MainPage:

// swiper的第五个页面数据
private dataPageFive: GridItemInfo[] = [
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
  { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") },
];

// swiper第五个page
this.GridBuilderFunction(this.dataPageFive, Const.GRID_THIRD_HEIGHT, Const.GRID_THIRD_TEMPLATE)

// 如果页面是swiper的第一个page的话,上方有其他组件,swiper需要降低高度
if (page === this.dataPageOne) {
  Column()
    .margin({ top: this.swiperDistance })
    .height($r('app.float.swipersmoothvariation_blank_height'))
    .width($r('app.float.swipersmoothvariation_scroll_width'))
} else if (page === this.dataPageFive){
  Column()
    .margin(height === Const.GRID_THIRD_HEIGHT ?
      { top: Const.SWIPER_OFFSET + this.swiperDistance - Const.GRID_SINGLE_HEIGHT} :
      { top: Const.SWIPER_OFFSET + this.swiperDistance})
    .height($r('app.float.swipersmoothvariation_blank_height'))
    .width($r('app.float.swipersmoothvariation_scroll_width'))
} else {
  Column()
    .margin(height === Const.GRID_SINGLE_HEIGHT ?
      { top: Const.SWIPER_OFFSET + Const.GRID_SINGLE_HEIGHT + this.swiperDistance } :
      { top: Const.SWIPER_OFFSET + this.swiperDistance })
    .height($r('app.float.swipersmoothvariation_blank_height'))
    .width($r('app.float.swipersmoothvariation_scroll_width'))
}

if (index === 0 && extraInfo.currentOffset < 0) {
  this.swiperDistance = extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE;
} else if (index === 1 && extraInfo.currentOffset > 0) {
  this.swiperDistance = extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE - Const.SMALL_FONT_SIZE;
} else if (index === 2 && extraInfo.currentOffset < 0) {
  this.swiperDistance = extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE - Const.SMALL_FONT_SIZE;
} else if (index === 3 && extraInfo.currentOffset > 0) {
  this.swiperDistance = extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE - Const.SMALL_FONT_SIZE;
} else if (index === 4 && extraInfo.currentOffset < 0) {
  this.swiperDistance = extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.GRID_SINGLE_HEIGHT - Const.SMALL_FONT_SIZE + Const.GRID_THIRD_HEIGHT;
}

if (targetIndex === 0) {
  this.swiperDistance = 0;
} else if (targetIndex === 1 || targetIndex === 2 || targetIndex === 3) {
  this.swiperDistance = -Const.SMALL_FONT_SIZE;
} else if (targetIndex === 4){
  this.swiperDistance = -Const.SMALL_FONT_SIZE - Const.GRID_SINGLE_HEIGHT + Const.GRID_DOUBLE_HEIGHT;
}

// 第五页三行
static readonly GRID_THIRD_HEIGHT: number = 195;
static readonly GRID_THIRD_TEMPLATE: string = '1fr 1fr 1fr';

CommonConstants:

/**
 * 代表自定义类型数据的接口。
 * @interface
 * @property {Resource} name - 名字。
 * @property {Resource} image - 图片。
 * @property {Resource} prompt - 角标。
 */
interface GridItemInfo {
  name: Resource
  image: Resource
  prompt?: Resource
};

@CustomDialog
struct CustomDialogExample {
  controller: CustomDialogController;
  build() {
    Column() {
      Text($r('app.string.swipersmoothvariation_message_custom_dialog'))
    }
    .justifyContent(FlexAlign.Start)
  }
}

/**
 * 自定义组件
 * 功能:自定义一个图标+文字组合
 */
@Component
struct ViewItem {
  item: GridItemInfo = {
    name: $r('app.string.swipersmoothvariation_name_pageone'),
    image: "app.media.swipersmoothvariation_taxi",
    prompt: $r('app.string.swipersmoothvariation_first_prompt')
  };
  build() {
    Column() {
      Stack() {
        Image(this.item.image)
          .height($r('app.float.swipersmoothvariation_app_side_length'))
          .width($r('app.float.swipersmoothvariation_app_side_length'))
        Text(this.item.prompt)
          .fontColor($r('app.color.swipersmoothvariation_color_white'))
          .fontSize($r('app.float.swipersmoothvariation_badge_font_size'))
          .backgroundColor($r('app.color.swipersmoothvariation_badge_color'))
          .borderRadius({
            topLeft: $r('app.float.swipersmoothvariation_text_border_radius'),
            topRight: $r('app.float.swipersmoothvariation_text_border_radius'),
            bottomRight: $r('app.float.swipersmoothvariation_text_border_radius')
          })
          .textAlign(TextAlign.Center)
          .width($r('app.integer.swipersmoothvariation_item_width'))
          .height($r('app.integer.swipersmoothvariation_item_height'))
          .margin({
            top: $r('app.integer.swipersmoothvariation_margin_top'),
            left: $r('app.integer.swipersmoothvariation_margin_left')
          })
          .visibility(this.item.prompt !== undefined ? Visibility.Visible : Visibility.None)
      }
      .width(Const.FULL_PERCENT)
      Text(this.item.name)
        .margin({ top: $r('app.float.swipersmoothvariation_item_text_offset') })
        .fontSize($r('app.float.swipersmoothvariation_small_font_size'))
    }
  }
}

/**
 * 页面高度随着swiper高度平滑变化
 * 滑动swiper,页面高度随着swiper高度平滑变化
 * 效果:当不同swiper页面的高度发生变化时,下方页面的高度也会随着一起平滑的变化
 */
@Entry
@Component
export struct SwiperSmoothVariation {
  // scroller控制器初始化
  private gridScroller: Scroller = new Scroller();
  private dialog: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample()
  });
  // swiper的第一个页面数据
  private dataPageOne: GridItemInfo[] = [
  //第一页数据
  ];
  // swiper的第二个页面数据
  private dataPageTwo: GridItemInfo[] = [
  //第二页数据
  ];
  // swiper的第三个页面数据
  private dataPageThree: GridItemInfo[] = [
  //第三页数据
  ];
  // swiper的第四个页面数据
  private dataPageFour: GridItemInfo[] = [
    { name: $r('app.string.swipersmoothvariation_name_pagefour'), image: $r("app.media.swipersmoothvariation_taxi") }
  ];
  private dataPageFive: GridItemInfo[] = [
  //第五页数据
  ];
  // 下方页面对应swiper上下位置的变化
  @State swiperDistance: number = 0;
  @State targetIndex: number = 0;
  // 自定义构建函数,生成每一个swiper页面的函数
  @Builder
  GridBuilderFunction(page: GridItemInfo[], height: number, template: string) {
    Column() {
      Grid(this.gridScroller) {
        ForEach(page, (item: GridItemInfo) => {
          GridItem() {
            ViewItem({ item: item })
              .onClick(() => {
                this.dialog.open();
              })
          }
          .width(Const.FULL_PERCENT)
          .height(height)
          .width($r('app.float.swipersmoothvariation_scroll_width'))
        })
      }
      .height(height)
      .width($r('app.string.swipersmoothvariation_width_full'))
      .borderRadius($r('app.float.swipersmoothvariation_border_radius'))
      .edgeEffect(EdgeEffect.None)
      .columnsTemplate(Const.COLUMN_TEMPLATE)
      .rowsTemplate(template)
      // 如果页面是swiper的第一个page的话,上方有其他组件,swiper需要降低高度
      if (page === this.dataPageOne) {
        Column()
          .margin({ top: this.swiperDistance })
          .height($r('app.float.swipersmoothvariation_blank_height'))
          .width($r('app.float.swipersmoothvariation_scroll_width'))
      } else {
        Column()
          .margin(height === Const.GRID_SINGLE_HEIGHT ?
            { top: Const.SWIPER_OFFSET + Const.GRID_SINGLE_HEIGHT + this.swiperDistance } :
            { top: Const.SWIPER_OFFSET + this.swiperDistance })
          .height($r('app.float.swipersmoothvariation_blank_height'))
          .width($r('app.float.swipersmoothvariation_scroll_width'))
      }
    }
    .margin(page === this.dataPageOne ? { top: $r('app.float.swipersmoothvariation_swiper_offset') } : 0)
  }

  // 创建一个stack组件,用来显示swiper和下方页面(下方column),创建swiper组件用来显示滑动效果
  // 当swiper滑动时,下方页面(column)高度也会变化
  build() {
    Stack() {
      Swiper() {
        Column() {
          Stack() {
            Text('功能栏')
              .textAlign(TextAlign.Center)
              .margin({
                top: $r('app.integer.swipersmoothvariation_margin_small'),
                left: $r('app.integer.swipersmoothvariation_default_padding')
              })
            // swiper第一个page
            this.GridBuilderFunction(this.dataPageOne, Const.GRID_DOUBLE_HEIGHT, Const.GRID_TEMPLATE)
          }
          .alignContent(Alignment.TopStart)
        }
        // swiper第二个page
        this.GridBuilderFunction(this.dataPageTwo, Const.GRID_DOUBLE_HEIGHT, Const.GRID_TEMPLATE)
        // swiper第四个page
        this.GridBuilderFunction(this.dataPageFour, Const.GRID_SINGLE_HEIGHT, Const.GRID_SINGLE_TEMPLATE)
        // swiper第三个page
        // this.GridBuilderFunction(this.dataPageThree, Const.GRID_DOUBLE_HEIGHT, Const.GRID_TEMPLATE)
      }
      .width($r('app.string.swipersmoothvariation_width_full'))
      .backgroundColor($r('app.color.swipersmoothvariation_color_white'))
      .borderRadius($r('app.integer.swipersmoothvariation_border_radius'))
      .margin({ top: $r('app.float.swipersmoothvariation_margin_fifteen') })
      .effectMode(EdgeEffect.None)
      .loop(false)
      // 知识点: Swiper组件绑定onGestureSwipe事件,在页面跟手滑动过程中,逐帧触发该回调
      // 性能知识点: onGestureSwipe属于频繁回调,不建议在onGestureSwipe做耗时和冗余操作
      .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
        animateTo({
          duration: Const.DURATION_SWIPER,
          curve: Curve.EaseOut,
          playMode: PlayMode.Normal,
          onFinish: () => {
            console.info('play end');
          }
        }, () => { // 通过左右滑动的距离来计算对应的上下位置的变化
          if (index === 0) {
            this.swiperDistance =
              extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE;
          } else if (index === 1) {
            this.swiperDistance =
              extraInfo.currentOffset / Const.SCROLL_WIDTH * 50 - 13;
          } else if (index === 2) {
            this.swiperDistance =
              extraInfo.currentOffset / Const.SCROLL_WIDTH * 50 -78;
          } else if (index === 3) {
            this.swiperDistance =
              -extraInfo.currentOffset / Const.SCROLL_WIDTH * Const.SMALL_FONT_SIZE - Const.SMALL_FONT_SIZE * 4;
          }
        })
      })
      // 平滑变化的动画效果
      .onAnimationStart((_: number, targetIndex: number) => {
        animateTo({
          duration: Const.DURATION_DOWN_PAGE,
          curve: Curve.EaseOut,
          playMode: PlayMode.Normal,
          onFinish: () => {
            console.info('play end');
          }
        }, () => {
          if (targetIndex === 0) {
            this.swiperDistance = 0;
          } else if (targetIndex === 1 || targetIndex === 3) {
            this.swiperDistance = -Const.SMALL_FONT_SIZE;
          } else {
            this.swiperDistance = -Const.SMALL_FONT_SIZE - Const.GRID_SINGLE_HEIGHT;
          }
        })
      })
      // swiper指示器
      .indicator(new DotIndicator()
        .selectedItemWidth($r('app.float.swipersmoothvariation_select_item_width'))
        .selectedItemHeight($r('app.float.swipersmoothvariation_select_item_height'))
        .itemWidth($r('app.float.swipersmoothvariation_default_item_width'))
        .itemHeight($r('app.float.swipersmoothvariation_default_item_height'))
        .selectedColor($r('app.color.swipersmoothvariation_swiper_selected_color'))
        .color($r('app.color.swipersmoothvariation_swiper_unselected_color')))
      // swiper下方的页面
      Image($r("app.media.swipersmoothvariation_test"))
        .height($r('app.integer.swipersmoothvariation_height_1'))
        .borderRadius($r('app.integer.swipersmoothvariation_border_radius'))
        .width($r('app.string.swipersmoothvariation_width_full'))
        .offset({ y: this.swiperDistance })
        .margin({ top: $r('app.float.swipersmoothvariation_colum_offset_one') })
      Text('高度:' + this.swiperDistance)
    }
    .backgroundColor($r('app.color.swipersmoothvariation_stack_color'))
    .padding($r('app.integer.swipersmoothvariation_default_padding'))
    .alignContent(Alignment.TopStart)
  }
}

更多关于HarmonyOS 鸿蒙Next swiper的页面高度不同时的滑动效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


姓名

张三

职位

软件工程师

兴趣爱好

  • 阅读
  • 篮球
  • 电影

Alt text

希望HarmonyOS能继续优化系统稳定性,减少崩溃和重启的情况。

每个页面高度不可知的情况下如果平滑移动。

可以采用动态布局和过渡效果来解决问题。

动态计算高度并传递给下部组件,以 ArkUI 为例

首先,在Swiper的每个页面组件中,需要能够获取自身的高度。可以通过@Size装饰器来实现。假设Swiper的每个页面是一个自定义组件SwiperPage

<MyLowerComponent height={this.swiperPagesHeight[this.currentIndex]} />

在这里,MyComponent1和MyComponent2等SwiperPage中的组件在自身大小发生变化(高度变化)时,通过onSize事件将高度信息传递给MySwiper组件。MySwiper组件则维护一个数组swiperPagesHeight来存储每个SwiperPage的高度,并将当前SwiperPage的高度传递给MyLowerComponent。

另一种方法是,添加过渡效果,为了使下部组件的位置变化更加平滑,可以在MyLowerComponent的样式中添加过渡效果。

@Component
struct MyLowerComponent {
  @Prop height: number;
  build() {
    return (
      <Divider
        style={{
          height: this.height + 'px',
          transition: 'height 0.3s ease-in-out'
        }}
      />
    );
  }
}

上述代码中,当height属性发生变化时,Divider的高度会在 0.3 秒内以缓入缓出的方式进行过渡,从而实现平滑移动的效果。

啊这~~不是ArkUI吧,

![图片](https://example.com/image.png)

在HarmonyOS(鸿蒙)系统中,当使用Next swiper组件且页面高度不同时,滑动效果的处理主要依赖于swiper组件自身的布局管理和动画特性。鸿蒙系统为swiper组件提供了灵活的布局支持,可以适应不同高度的页面。

默认情况下,swiper组件会按照子页面的实际高度进行布局,滑动时会根据当前页面的高度进行平滑过渡。如果页面高度不一致,swiper会自动调整滑动动画,确保过渡效果自然流畅。

为了优化滑动体验,可以考虑以下几点:

  • 确保每个swiper页面的布局合理,避免页面内元素过多导致性能问题。
  • 利用鸿蒙的布局容器(如Stack、Column等)来精细控制页面布局,确保页面高度符合预期。
  • 如果需要自定义滑动动画,可以通过设置swiper的动画属性(如滚动速度、滚动惯性等)来调整。

然而,鸿蒙系统本身已经对swiper组件的滑动效果进行了优化,以适应不同高度的页面。因此,在大多数情况下,开发者无需进行额外的配置即可获得良好的滑动体验。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部