鴻蒙開發(fā)實(shí)戰(zhàn)案例:視頻卡片和列表區(qū)域的聯(lián)動(dòng)滾動(dòng)思路

介紹

本示例使用Scroll和List組件嵌套,通過(guò)List組件的滾動(dòng)控制器和nestedScroll屬性實(shí)現(xiàn)了視頻卡片和列表區(qū)域的聯(lián)動(dòng)滾動(dòng)場(chǎng)景。

效果圖預(yù)覽

使用說(shuō)明

  1. 向上滑動(dòng)列表,頁(yè)面向上滾動(dòng)到末尾后隱藏視頻,繼續(xù)向上滑動(dòng),卡片吸頂,列表開始滾動(dòng),列表滾動(dòng)到底觸發(fā)回彈效果。
  2. 向下滑動(dòng)列表,列表先滾動(dòng)到頭部后,頁(yè)面向下滾動(dòng),視頻顯示,繼續(xù)向下滑動(dòng)到頁(yè)面頭部,頁(yè)面上方觸發(fā)回彈效果。
  3. 點(diǎn)擊視頻卡片中的播放按鈕切換視頻播放狀態(tài)。
  4. 視頻卡片點(diǎn)擊上一條、下一條時(shí),通過(guò)List的滾動(dòng)控制器控制列表滾動(dòng)到指定位置,視頻卡片不發(fā)生滾動(dòng)。
  5. 點(diǎn)擊列表項(xiàng),列表發(fā)生滾動(dòng),視頻卡片不滾動(dòng)。

實(shí)現(xiàn)思路

  1. 初始化新聞列表數(shù)據(jù) NEWS_LIST_DATA,通過(guò)狀態(tài)變量currentPlayNews和currentIndex跟蹤當(dāng)前播放的新聞。
  // 當(dāng)前播放的新聞
  @State currentPlayNews: NewsItem = new NewsItem('', '');
  // 當(dāng)前播放的新聞在列表中的下標(biāo)
  @State currentIndex: number = 0;
  
  aboutToAppear() {
    // 新聞列表數(shù)據(jù)初始化
    NEWS_LIST_DATA.forEach((news: NewsItem) => {
      this.newsList.pushData(news);
    })
    this.currentPlayNews = this.newsList.getData(this.currentIndex);
    ...
  }
  1. 為了解決新聞列表與外層Scroll容器嵌套時(shí)的滾動(dòng)沖突問題,給新聞列表List設(shè)置 nestedScroll 屬性,指定列表向末尾端和起始端滾動(dòng)時(shí)與外層Scroll的嵌套滾動(dòng)方式。
  List({ scroller: this.scroller }) {
    ...
  }
  .nestedScroll({
    scrollForward: NestedScrollMode.PARENT_FIRST, // 可滾動(dòng)組件往末尾端滾動(dòng)時(shí)的嵌套滾動(dòng)選項(xiàng),父組件先滾動(dòng),父組件滾動(dòng)到邊緣以后自身滾動(dòng)。
    scrollBackward: NestedScrollMode.SELF_FIRST // 可滾動(dòng)組件往起始端滾動(dòng)時(shí)的嵌套滾動(dòng)選項(xiàng),自身先滾動(dòng),自身滾動(dòng)到邊緣以后父組件滾動(dòng)。
  })
  1. 為了實(shí)現(xiàn)視頻卡片的吸頂效果, Scroll 容器的內(nèi)容高度使用 calc 計(jì)算屬性設(shè)置為 Scroll 容器高度和視頻高度的和,使 Scroll 滾動(dòng)到尾部邊緣時(shí),視頻隱藏,視頻卡片吸頂。
  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. 新聞列表組件設(shè)置 layoutWeight 為1,使列表自動(dòng)占滿 Scroll 內(nèi)容的剩余空間,當(dāng)視頻卡片吸頂時(shí)新聞列表可以完全顯示,并且當(dāng)新聞標(biāo)題改變導(dǎo)致卡片高度發(fā)生變化時(shí),新聞列表組件高度也相應(yīng)變化。
  List({ scroller: this.scroller }) {
    ...
  }
  .layoutWeight(Constants.LAYOUT_WEIGHT)
  1. 通過(guò)狀態(tài)變量isHideVideo修改視頻的高度實(shí)現(xiàn)顯隱,Scroll滾動(dòng)到末尾時(shí)隱藏視頻,視頻已隱藏情況下, Scroll向下滾動(dòng)時(shí)顯示視頻。
  // 是否隱藏視頻區(qū)域
  @State @Watch('onIsHideVideoChange') isHideVideo: boolean = false;
  
  Scroll(this.scroller) {
    ...
  }
  // TODO: 性能知識(shí)點(diǎn):onScroll屬于頻繁回調(diào)接口,應(yīng)該避免在內(nèi)部進(jìn)行冗余和耗時(shí)操作,例如避免打印日志
  .onScroll((xOffset: number, yOffset: number) => {
    // 視頻已隱藏情況下, Scroll向下滾動(dòng)時(shí)顯示視頻
    if (yOffset < 0 && this.isHideVideo) {
      this.isHideVideo = false;
    }
  })
  .onReachEnd(() => {
    // Scroll滾動(dòng)到末尾時(shí)隱藏視頻
    this.isHideVideo = true;
  })
  
  Stack({ alignContent: Alignment.Bottom }) {
    ...
  }
  .width($r('app.string.video_linkage_list_full_size'))
   // 修改視頻的高度實(shí)現(xiàn)顯隱控制
  .height(this.isHideVideo ? 0 : Constants.VIDEO_HEIGHT)
  1. 在狀態(tài)變量isHideVideo的監(jiān)聽回調(diào)中,根據(jù)視頻的顯隱狀態(tài)修改視頻卡片的上邊距保持Scroll內(nèi)容高度不變,避免滾動(dòng)混亂。
  // TODO:知識(shí)點(diǎn):根據(jù)視頻顯隱狀態(tài)修改邊距,使用邊距代替video占位,使Scroll容器內(nèi)容高度不變,可以向下滾動(dòng)顯示視頻,并且避免滾動(dòng)混亂
  onIsHideVideoChange() {
    if (!this.isHideVideo) {
      // 視頻顯示,視頻卡片上邊距減去視頻高度
      this.videoMarginTop -= Constants.VIDEO_HEIGHT;
    } else {
      // 視頻隱藏,視頻卡片上邊距加上視頻高度
      this.videoMarginTop += Constants.VIDEO_HEIGHT;
    }
  }
  1. 在視頻卡片中上一條、下一條按鈕的點(diǎn)擊回調(diào)中修改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. 在新聞列表組件中監(jiān)聽狀態(tài)變量currentIndex,根據(jù)選中項(xiàng)的索引值計(jì)算列表的滾動(dòng)偏移。
  // TODO:知識(shí)點(diǎn):監(jiān)聽currentIndex的變化,視頻播放卡片切換新聞和點(diǎn)擊列表項(xiàng)切換新聞時(shí)修改currentIndex,根據(jù)下標(biāo)計(jì)算列表的滾動(dòng)偏移
  onCurrentIndexChange() {
    // 選中的列表項(xiàng)下標(biāo)大于3時(shí),列表向上滾動(dòng),滾動(dòng)到與列表顯示區(qū)域內(nèi)上方間隔3個(gè)列表項(xiàng)或列表尾部時(shí)停止。
    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 {
      // 選中的列表項(xiàng)下標(biāo)小于等于3時(shí),列表滾動(dòng)至頭部
      this.scroller.scrollTo({ yOffset: 0, xOffset: 0 });
    }
  }

高性能知識(shí)點(diǎn)

  1. 本示例使用了LazyForEach進(jìn)行數(shù)據(jù)懶加載,List布局時(shí)會(huì)根據(jù)可視區(qū)域按需創(chuàng)建ListItem組件,并在ListItem滑出可視區(qū)域外時(shí)銷毀以降低內(nèi)存占用。

工程結(jié)構(gòu)&模塊類型

   videolinkagelist                              // har類型
   |---/src/main/ets/model                        
   |   |---NewsListDataSource.ets                // 數(shù)據(jù)模型層-列表數(shù)據(jù)模型 
   |   |---NewsItemModel.ets                     // 數(shù)據(jù)模型層-列表項(xiàng)數(shù)據(jù)模型
   |---/src/main/ets/pages                        
   |   |---VideoLinkageList.ets                  // 視圖層-主頁(yè)面
   |---/src/main/ets/mock                        
   |   |---NewsListData.ets                      // 新聞列表mock數(shù)據(jù)
   |---/src/main/ets/common                        
   |   |---Constants.ets                         // 常量數(shù)據(jù)

寫在最后

  • 如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助,我想邀請(qǐng)你幫我三個(gè)小忙:
  • 點(diǎn)贊,轉(zhuǎn)發(fā),有你們的 『點(diǎn)贊和評(píng)論』,才是我創(chuàng)造的動(dòng)力。
  • 關(guān)注小編,同時(shí)可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識(shí)。
  • 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識(shí)點(diǎn),請(qǐng)移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容