鸿蒙开发实战案例:视频卡片和列表区域的联动滚动思路

介绍

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

效果图预览

使用说明

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

实现思路

  1. 初始化新闻列表数据 NEWS_LIST_DATA,通过状态变量currentPlayNews和currentIndex跟踪当前播放的新闻。
  // 当前播放的新闻
  @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);
    ...
  }
  1. 为了解决新闻列表与外层Scroll容器嵌套时的滚动冲突问题,给新闻列表List设置 nestedScroll 属性,指定列表向末尾端和起始端滚动时与外层Scroll的嵌套滚动方式。
  List({ scroller: this.scroller }) {
    ...
  }
  .nestedScroll({
    scrollForward: NestedScrollMode.PARENT_FIRST, // 可滚动组件往末尾端滚动时的嵌套滚动选项,父组件先滚动,父组件滚动到边缘以后自身滚动。
    scrollBackward: NestedScrollMode.SELF_FIRST // 可滚动组件往起始端滚动时的嵌套滚动选项,自身先滚动,自身滚动到边缘以后父组件滚动。
  })
  1. 为了实现视频卡片的吸顶效果, Scroll 容器的内容高度使用 calc 计算属性设置为 Scroll 容器高度和视频高度的和,使 Scroll 滚动到尾部边缘时,视频隐藏,视频卡片吸顶。
  Scroll(this.scroller) {
    Column() {
      NewsVideoView({
        currentPlayNews: this.currentPlayNews,
        currentIndex: this.currentIndex,
        newsList: this.newsList,
        isHideVideo: this.isHideVideo,
        scrollHeight: this.scrollHeight,
        newsListHeight: this.newsListHeight,
      })
        .margin({ top: this.videoMarginTop, bottom: $r('app.integer.video_linkage_list_video_card_margin_bottom') })
      NewsListView({
        currentPlayNews: this.currentPlayNews,
        currentIndex: this.currentIndex,
        newsList: this.newsList,
        isHideVideo: this.isHideVideo,
        newsListHeight: this.newsListHeight
      })
    }
    .height(`calc(${Constants.VIDEO_HEIGHT}vp + 100%)`)
  }
  1. 新闻列表组件设置 layoutWeight 为1,使列表自动占满 Scroll 内容的剩余空间,当视频卡片吸顶时新闻列表可以完全显示,并且当新闻标题改变导致卡片高度发生变化时,新闻列表组件高度也相应变化。
  List({ scroller: this.scroller }) {
    ...
  }
  .layoutWeight(Constants.LAYOUT_WEIGHT)
  1. 通过状态变量isHideVideo修改视频的高度实现显隐,Scroll滚动到末尾时隐藏视频,视频已隐藏情况下, Scroll向下滚动时显示视频。
  // 是否隐藏视频区域
  @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)
  1. 在状态变量isHideVideo的监听回调中,根据视频的显隐状态修改视频卡片的上边距保持Scroll内容高度不变,避免滚动混乱。
  // TODO:知识点:根据视频显隐状态修改边距,使用边距代替video占位,使Scroll容器内容高度不变,可以向下滚动显示视频,并且避免滚动混乱
  onIsHideVideoChange() {
    if (!this.isHideVideo) {
      // 视频显示,视频卡片上边距减去视频高度
      this.videoMarginTop -= Constants.VIDEO_HEIGHT;
    } else {
      // 视频隐藏,视频卡片上边距加上视频高度
      this.videoMarginTop += Constants.VIDEO_HEIGHT;
    }
  }
  1. 在视频卡片中上一条、下一条按钮的点击回调中修改currentIndex和currentPlayNews。
  // 上一条
  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')
        });
      }
    })
  1. 在新闻列表组件中监听状态变量currentIndex,根据选中项的索引值计算列表的滚动偏移。
  // 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 });
    }
  }

高性能知识点

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

工程结构&模块类型

   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                         // 常量数据

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习知识点,请移步前往小编:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,156评论 6 529
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,866评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,880评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,398评论 1 308
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,202评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,743评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,822评论 3 438
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,962评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,476评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,444评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,579评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,129评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,840评论 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,231评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,487评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,177评论 3 388
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,568评论 2 370

推荐阅读更多精彩内容