如何实现视频流列表功能
===========

#### 问题现象

应用内有视频流列表功能，用户点击进入视频列表页面后固定推送三十个视频，请问该如何实现？

#### 背景知识

在HarmonyOS开发中，[Swiper组件](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-swiper)主要用于创建轮播图或滑动视图，而[LazyForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach)用于实现数据的懒加载，两者结合可以有效地优化固定数量数据集的加载和显示性能。视频播放可以通过[Video组件](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video)或[AVPlayer接口](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-media-avplayer)实现。

#### 解决方案

*   **功能一：视频播放。**
    *   **实现原理：** 视频播放可以选择Video组件或者AVPlayer接口，本文以Video组件为例，结合Stack实现自定义视频控制栏和功能。如果对拓展性有较高要求，可以自行选择AVPlayer接口实现。
    *   **示例代码：**
        
        @Component
        struct VideoShow {
          @Prop videoSrc: string = '';
          controller: VideoController = new VideoController();
          @State isPlay: boolean = false;_//是否播放_
          @Prop isAutoPlay: boolean = false;_//是否自动播放_
        
          build() {
            Column() {
              Stack() {
                Video({src: $rawfile(this.videoSrc), controller: this.controller})
                  .controls(false)
                  .autoPlay(this.isAutoPlay)
                  .onClick(() => {
                    this.isPlay = !this.isPlay;
                    if (this.isPlay) { _// 控制视频播放状态_
                      this.controller.pause();
                    } else {
                      this.controller.start();
                    }
                  })
              }
            }
            .width('100%')
            .height('100%')
          }
        }
        
*   **功能二：滑动列表。**
    *   **实现原理：** 将Swiper组件设置为竖向滑动，实现列表的整页滑动，视频数据则使用LazyForEach加载。同时设置页面沉浸式，优化视频观感。
    *   **示例代码：**
        
        import { window } from '@kit.ArkUI';
        import { common } from '@kit.AbilityKit';
        import { BusinessError } from '@kit.BasicServicesKit';
        import { hilog } from '@kit.PerformanceAnalysisKit';
        
        
        class MyDataSourceLTwo implements IDataSource {_// 加载数据_
          private list: string\[\] = \[\];
        
        
          constructor(list: string\[\]) {
            this.list = list;
          }
        
        
          totalCount(): number {
            return this.list.length;
          }
        
        
          getData(index: number): string {
            return this.list\[index\];
          }
        
        
          registerDataChangeListener(): void {
            console.info(\`注册数据变化监听\`);
          }
        
        
          unregisterDataChangeListener() {
            console.info(\`取消数据变化监听\`);
          }
        }
        @Entry
        @Component
        struct ShortVideo {
          private data: MyDataSourceLTwo = new MyDataSourceLTwo(\[\]);
          @State nowNum: number = 0;
          context: Context | undefined = undefined;
        
        
          setVideoFull() { _// 设置沉浸式_
            let windowClass: window.Window | undefined = undefined;
            try {
              let promise = window.getLastWindow(this.context);
              promise.then((data) => {
                windowClass = data;
                windowClass.setWindowLayoutFullScreen(true).then(() => {
                  hilog.info(0x0000, 'testTag', 'Succeeded in setting the window layout to full-screen mode.');
                }).catch((err: BusinessError) => {
                  hilog.error(0x0000, 'testTag',
                    'Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
                });
              _  //状态栏隐藏_
                windowClass.setSpecificSystemBarEnabled('status', true).then(() => {
                  hilog.info(0x0000, 'testTag', 'Succeeded in setting the status bar to be invisible.');
                }).catch((err: BusinessError) => {
                  hilog.error(0x0000, 'testTag', \`Failed to set the status bar to be invisible. Code is ${err.code},
                  message is ${err.message}\`);
                });
               _ //导航条隐藏_
                windowClass.setSpecificSystemBarEnabled('navigationIndicator', false).then(() => {
                  hilog.info(0x0000, 'testTag', 'Succeed in setting the system bar to be invisible');
                }).catch((err: BusinessError) => {
                  hilog.error(0x0000, 'testTag',
                    \`Failed to set the system bar to be invisible. Cause code: ${err.code}, message: ${err.message}\`);
                });
              }).catch((err: BusinessError) => {
                console.error('getLastWindow error', err);
              });
            } catch (e) {
              console.error('setScreenOrientation error');
            }
          }
        
        
          aboutToAppear(): void {
            let list: string\[\] = \[\];
            for (let i = 1; i <= 3; i++) {
              list.push('videoTest.mp4');
            }
            this.data = new MyDataSourceLTwo(list);
            this.context = this.getUIContext().getHostContext() as common.Context;
            this.setVideoFull();
          }
        
        
          build() {
            Column() {
              Swiper() {
                LazyForEach(this.data, (item: string, index: number) => {
                  VideoShow({
                    videoSrc: this.nowNum === index ? item : '',
                    isAutoPlay: this.nowNum === index ? true : false
                  });
                })
              }
              .width('100%')
              .height('100%')
              .vertical(true) _// 设置竖向_
              .indicator(false) _// 设置不显示导航点_
              .loop(false) _// 设置不循环_
              .onChange((index: number) => {
                this.nowNum = index;
              })
            }
            .width('100%')
            .height('100%')
          }
        }
        

**完整示例代码：**

import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';


class MyDataSourceLTwo implements IDataSource {_// 加载数据_
  private list: string\[\] = \[\];


  constructor(list: string\[\]) {
    this.list = list;
  }


  totalCount(): number {
    return this.list.length;
  }


  getData(index: number): string {
    return this.list\[index\];
  }


  registerDataChangeListener(): void {
    console.info(\`注册数据变化监听\`);
  }


  unregisterDataChangeListener() {
    console.info(\`取消数据变化监听\`);
  }
}
@Entry
@Component
struct ShortVideo {
  private data: MyDataSourceLTwo = new MyDataSourceLTwo(\[\]);
  @State nowNum: number = 0;
  context: Context | undefined = undefined;


  setVideoFull() { _// 设置沉浸式_
    let windowClass: window.Window | undefined = undefined;
    try {
      let promise = window.getLastWindow(this.context);
      promise.then((data) => {
        windowClass = data;
        windowClass.setWindowLayoutFullScreen(true).then(() => {
          hilog.info(0x0000, 'testTag', 'Succeeded in setting the window layout to full-screen mode.');
        }).catch((err: BusinessError) => {
          hilog.error(0x0000, 'testTag',
            'Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
        });
       _ //状态栏隐藏_
        windowClass.setSpecificSystemBarEnabled('status', true).then(() => {
          hilog.info(0x0000, 'testTag', 'Succeeded in setting the status bar to be invisible.');
        }).catch((err: BusinessError) => {
          hilog.error(0x0000, 'testTag', \`Failed to set the status bar to be invisible. Code is ${err.code},
          message is ${err.message}\`);
        });
      _  //导航条隐藏_
        windowClass.setSpecificSystemBarEnabled('navigationIndicator', false).then(() => {
          hilog.info(0x0000, 'testTag', 'Succeed in setting the system bar to be invisible');
        }).catch((err: BusinessError) => {
          hilog.error(0x0000, 'testTag',
            \`Failed to set the system bar to be invisible. Cause code: ${err.code}, message: ${err.message}\`);
        });
      }).catch((err: BusinessError) => {
        console.error('getLastWindow error', err);
      });
    } catch (e) {
      console.error('setScreenOrientation error');
    }
  }


  aboutToAppear(): void {
    let list: string\[\] = \[\];
    for (let i = 1; i <= 3; i++) {
      list.push('videoTest.mp4');
    }
    this.data = new MyDataSourceLTwo(list);
    this.context = this.getUIContext().getHostContext() as common.Context;
    this.setVideoFull();
  }


  build() {
    Column() {
      Swiper() {
        LazyForEach(this.data, (item: string, index: number) => {
          VideoShow({
            videoSrc: this.nowNum === index ? item : '',
            isAutoPlay: this.nowNum === index ? true : false
          });
        })
      }
      .width('100%')
      .height('100%')
      .vertical(true) _// 设置竖向_
      .indicator(false) _// 设置不显示导航点_
      .loop(false) _// 设置不循环_
      .onChange((index: number) => {
        this.nowNum = index;
      })
    }
    .width('100%')
    .height('100%')
  }
}


@Component
struct VideoShow {
  @Prop videoSrc: string = '';
  controller: VideoController = new VideoController();
  @State isPlay: boolean = false;_//是否播放_
  @Prop isAutoPlay: boolean = false;_//是否自动播放_

  build() {
    Column() {
      Stack() {
        Video({src: $rawfile(this.videoSrc), controller: this.controller})
          .controls(false)
          .autoPlay(this.isAutoPlay)
          .onClick(() => {
            this.isPlay = !this.isPlay;
            if (this.isPlay) { _// 控制视频播放状态_
              this.controller.pause();
            } else {
              this.controller.start();
            }
          })
      }
    }
    .width('100%')
    .height('100%')
  }
}

效果图：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/0b/v3/uYWW81MESFuW_Hwy-X2L6g/zh-cn_image_0000002658841819.png?HW-CC-KV=V1&HW-CC-Date=20260709T134055Z&HW-CC-Expire=86400&HW-CC-Sign=0675EBD68ADB32D8D551BAEE04DEED0C3C59D9D42C31C9A6D6075BE2883C4E91 "点击放大")

#### 常见FAQ

Q：当加载数据量较大时，Swiper组件会出现加载缓慢、丢帧的问题，该如何优化？

A：可以为LazyForEach懒加载设置[cachedCount](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-swiper#cachedcount8)来指定缓存数量，或者使用[@Reusable](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-reusable)复用组件，具体实现可参考：[Swiper组件加载丢帧优化](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-swiper_high_performance_development_guide)。