HarmonyOS开发实战案例:Scroll和List组件嵌套实现视频卡片和列表区域

介绍

本示例使用Scroll和List组件嵌套,通过List组件的滚动控制器和nestedScroll属性实现了视频卡片和列表区域的联动滚动场景。

效果图预览

使用说明

  1. 向上滑动列表,页面向上滚动到末尾后隐藏视频,继续向上滑动,卡片吸顶,列表开始滚动,列表滚动到底触发回弹效果。
  2. 向下滑动列表,列表先滚动到头部后,页面向下滚动,视频显示,继续向下滑动到页面头部,页面上方触发回弹效果。
  3. 点击视频卡片中的播放按钮切换视频播放状态。
  4. 视频卡片点击上一条、下一条时,通过List的滚动控制器控制列表滚动到指定位置,视频卡片不发生滚动。
  5. 点击列表项,列表发生滚动,视频卡片不滚动。

实现思路

  • 初始化新闻列表数据 NEWS_LIST_DATA,通过状态变量currentPlayNews和currentIndex跟踪当前播放的新闻。源码参考VideoLinkageList.ets
  // 当前播放的新闻
  @State currentPlayNews: NewsItem = new NewsItem('', '');
  // 当前播放的新闻在列表中的下标
  @State currentIndex: number = 0;
  
  aboutToAppear() {
    // 新闻列表数据初始化
    NEWS_LIST_DATA.forEach((news: NewsItem) => {
      this.newsList.pushData(news);
    })
    this.currentPlayNews = this.newsList.getData(this.currentIndex);
    ...
  }
  • 为了解决新闻列表与外层Scroll容器嵌套时的滚动冲突问题,给新闻列表List设置 nestedScroll 属性,指定列表向末尾端和起始端滚动时与外层Scroll的嵌套滚动方式。源码参考VideoLinkageList.ets
List({ scroller: this.scroller }) {
    ...
  }
  .nestedScroll({
    scrollForward: NestedScrollMode.PARENT_FIRST, // 可滚动组件往末尾端滚动时的嵌套滚动选项,父组件先滚动,父组件滚动到边缘以后自身滚动。
    scrollBackward: NestedScrollMode.SELF_FIRST // 可滚动组件往起始端滚动时的嵌套滚动选项,自身先滚动,自身滚动到边缘以后父组件滚动。
  })
  • 为了实现视频卡片的吸顶效果,需要根据 Scroll 容器高度和隐藏视频后视频卡片的高度计算新闻列表的高度,使 Scroll 滚动到尾部边缘时,视频隐藏,视频卡片吸顶,新闻列表 List 完全显示。源码参考VideoLinkageList.ets
  • 计算Scroll容器高度
  async computeScrollHeight() {
    // 获取屏幕宽度
    let displayHeight: number = px2vp(display.getDefaultDisplaySync().height);
    // 获取应用窗口
    let appWindow: window.Window = await window.getLastWindow(getContext());
    // 获取系统默认区域,一般包含状态栏、导航栏
    let systemDefaultArea: window.AvoidArea = appWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
    // 获取导航条区域
    let navigationIndicatorArea: window.AvoidArea =
    appWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
    // 获取系统默认区域高度
    let systemDefaultHeight: number = px2vp(systemDefaultArea.topRect.height + systemDefaultArea.bottomRect.height);
    // 获取系统默认区域高度
    let navigationIndicatorHeight: number = px2vp(navigationIndicatorArea.bottomRect.height);
    // tabs高度
    let tabsHeight: number = displayHeight - systemDefaultHeight - navigationIndicatorHeight - Constants.NAVDESTINATION_PADDING_BOTTOM;
    this.scrollHeight = tabsHeight - Constants.TITLE_HEIGHT - Constants.TAB_BAR_HEIGHT - Constants.STROKE_WIDTH;
  }
  • 计算隐藏视频后视频卡片的高度
// 新闻标题高度
  const newsNameHeight = newValue.height as number;
  // 计算视频卡片上下外边距的和
  const videoCardVerticalMargin = Constants.VIDEO_CARD_MARGIN_TOP + Constants.VIDEO_CARD_MARGIN_BOTTOM;
  // 隐藏视频后视频卡片的高度
  const videoCardHeight = newsNameHeight + Constants.VIDEO_CONTROL_HEIGHT;
  • 计算新闻列表高度
/**
   * 计算新闻列表高度, 计算方式: 滚动容器高度 - 隐藏视频后视频卡片的高度 - 视频卡片上下外边距
   * 此时外层Scroll滚动到尾部边缘时,视频刚好隐藏,新闻列表List完全显示,继续向上滑动新闻列表自身开始滚动
   */
  this.newsListHeight = this.scrollHeight - videoCardHeight - videoCardVerticalMargin;
  • 由于视频卡片中不同新闻标题的行数可能不同,因此使用onAreaChange事件监听新闻标题高度变化,动态计算新闻列表高度,当标题的行数变化时改变列表的高度,保证滚动效果不发生改变。源码参考VideoLinkageList.ets
// 新闻标题
  Text(this.currentPlayNews.newsName)
    .width($r('app.string.video_linkage_list_full_size'))
    .fontSize($r('app.string.ohos_id_text_size_headline'))
    .fontWeight(FontWeight.Bolder)
    .padding($r('app.integer.video_linkage_list_news_name_padding'))
    // TODO: 性能知识点:onAreaChange属于频繁回调接口,应该避免在内部进行冗余和耗时操作,例如避免打印日志
    .onAreaChange((oldValue: Area, newValue: Area) => {
      // 切换新闻后获取新闻标题高度,如果标题高度发生变化后,重新计算新闻列表高度
      if (oldValue.height !== newValue.height) {
        // 新闻标题高度
        const newsNameHeight = newValue.height as number;
        // 计算视频卡片上下外边距的和
        const videoCardVerticalMargin = Constants.VIDEO_CARD_MARGIN_TOP + Constants.VIDEO_CARD_MARGIN_BOTTOM;
        // 隐藏视频后视频卡片的高度
        const videoCardHeight = newsNameHeight + Constants.VIDEO_CONTROL_HEIGHT;
        /**
         * 计算新闻列表高度, 计算方式: 滚动容器高度 - 隐藏视频后视频卡片的高度 - 视频卡片上下外边距
         * 此时外层Scroll滚动到尾部边缘时,视频刚好隐藏,新闻列表List完全显示,继续向上滑动新闻列表自身开始滚动
         */
        this.newsListHeight = this.scrollHeight - videoCardHeight - videoCardVerticalMargin;
      }
    })
  • 通过状态变量isHideVideo修改视频的高度实现显隐,Scroll滚动到末尾时隐藏视频,视频已隐藏情况下, Scroll向下滚动时显示视频。源码参考VideoLinkageList.ets
  // 是否隐藏视频区域
  @State @Watch('onIsHideVideoChange') isHideVideo: boolean = false;
  
  Scroll(this.scroller) {
    ...
  }
  // TODO: 性能知识点:onScroll属于频繁回调接口,应该避免在内部进行冗余和耗时操作,例如避免打印日志
  .onScroll((xOffset: number, yOffset: number) => {
    // 视频已隐藏情况下, Scroll向下滚动时显示视频
    if (yOffset < 0 && this.isHideVideo) {
      this.isHideVideo = false;
    }
  })
  .onReachEnd(() => {
    // Scroll滚动到末尾时隐藏视频
    this.isHideVideo = true;
  })
  
  Stack({ alignContent: Alignment.Bottom }) {
    ...
  }
  .width($r('app.string.video_linkage_list_full_size'))
   // 修改视频的高度实现显隐控制
  .height(this.isHideVideo ? 0 : Constants.VIDEO_HEIGHT)
  
  • 在状态变量isHideVideo的监听回调中,根据视频的显隐状态修改视频卡片的上边距保持Scroll内容高度不变,避免滚动混乱。源码参考VideoLinkageList.ets
 // TODO:知识点:根据视频显隐状态修改边距,使用边距代替video占位,使Scroll容器内容高度不变,可以向下滚动显示视频,并且避免滚动混乱
  onIsHideVideoChange() {
    if (!this.isHideVideo) {
      // 视频显示,视频卡片上边距减去视频高度
      this.videoMarginTop -= Constants.VIDEO_HEIGHT;
    } else {
      // 视频隐藏,视频卡片上边距加上视频高度
      this.videoMarginTop += Constants.VIDEO_HEIGHT;
    }
  }
  • 在视频卡片中上一条、下一条按钮的点击回调中修改currentIndex和currentPlayNews。源码参考VideoLinkageList.ets
 // 上一条
  Image($r('app.media.video_linkage_list_play_previous'))
    .height($r('app.integer.video_linkage_list_control_previous_next_height'))
    .onClick(() => {
      // 如果不是第一条,切换至上一条
      if (this.currentIndex > 0) {
        this.currentIndex--;
        this.currentPlayNews = this.newsList.getData(this.currentIndex);
      } else {
        promptAction.showToast({
          message: $r('app.string.video_linkage_list_first_data_toast')
        });
      }
    })
  ...
  // 下一条
  Image($r('app.media.video_linkage_list_play_next'))
    .height($r('app.integer.video_linkage_list_control_previous_next_height'))
    .onClick(() => {
      // 如果不是最后一条,切换至下一条
      if (this.currentIndex < this.newsList.totalCount() - 1) {
        this.currentIndex++;
        this.currentPlayNews = this.newsList.getData(this.currentIndex);
      } else {
        promptAction.showToast({
          message: $r('app.string.video_linkage_list_last_data_toast')
        });
      }
    })
  • 在新闻列表组件中监听状态变量currentIndex,根据选中项的索引值计算列表的滚动偏移。源码参考VideoLinkageList.ets
 // TODO:知识点:监听currentIndex的变化,视频播放卡片切换新闻和点击列表项切换新闻时修改currentIndex,根据下标计算列表的滚动偏移
  onCurrentIndexChange() {
    // 选中的列表项下标大于3时,列表向上滚动,滚动到与列表显示区域内上方间隔3个列表项或列表尾部时停止。
    if (this.currentIndex > Constants.NEWS_LIST_SCROLL_TO_INDEX) {
      this.scroller.scrollTo({
        yOffset: Constants.NEWS_LIST_ITEM_HEIGHT * (this.currentIndex - Constants.NEWS_LIST_SCROLL_TO_INDEX),
        xOffset: 0
      });
    } else {
      // 选中的列表项下标小于等于3时,列表滚动至头部
      this.scroller.scrollTo({ yOffset: 0, xOffset: 0 });
    }
  }

高性能知识点

  • 本示例使用了LazyForEach进行数据懒加载,List布局时会根据可视区域按需创建ListItem组件,并在ListItem滑出可视区域外时销毁以降低内存占用。参考文章正确使用LazyForEach优化

工程结构&模块类型

videolinkagelist                              // har类型
|---/src/main/ets/model                        
|   |---NewsListDataSource.ets                // 数据模型层-列表数据模型 
|   |---NewsItemModel.ets                     // 数据模型层-列表项数据模型
|---/src/main/ets/pages                        
|   |---VideoLinkageList.ets                  // 视图层-主页面
|---/src/main/ets/mock                        
|   |---NewsListData.ets                      // 新闻列表mock数据
|---/src/main/ets/common                        
|   |---Constants.ets                         // 常量数据

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

 点击→【纯血版鸿蒙全套最新学习资料】希望这一份鸿蒙学习资料能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

点击→纯血版全套鸿蒙HarmonyOS学习资料

2.视频学习资料+学习PDF文档

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

HarmonyOS Next 最新全套视频教程

​​

 (鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习资料(面试、文档、全套视频等)

                   

鸿蒙南向开发技术

​​

鸿蒙APP开发必备

​​

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

​​

《鸿蒙开发基础》

​​

《鸿蒙开发进阶》

《鸿蒙进阶实战》

​​


点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值