HarmonyOS鸿蒙Next中一种短视频软件首页的设计思路

HarmonyOS鸿蒙Next中一种短视频软件首页的设计思路 短视频软件首页刷视频的解决方案,使用swiper实现

获取视频信息

import { backInfo_video, IStandard, sendInfo_video, sendRequest } from '@st/basic';
import { emitter } from '@kit.BasicServicesKit';
import { backInfo_username, sendInfo_username } from '@st/basic/src/main/ets/utils/interfaces';

@Entry
@Component
export struct homeView {
  @State rawList: VideoItem[] = []
  @Provide
  @Watch("updateVideoList")
  activeIndex: number = 0
  @State page: number = 0
  scroller: Scroller = new Scroller()
  topList: string[] = ['经验', '热点', '直播', '精选', '团购', '关注', '商城', '本地', '推荐']
  @State clickIndex: number = this.topList.length - 1

  // TODO:视频滑动过去后从头开始播放,每次不保存视频进度,超出立即销毁

  async aboutToAppear() {
    await this.getVideoList()
  }

  aboutToDisappear() {
    emitter.off(0)
  }

  // 获取视频信息
  async getVideoList() {
    const resData = await sendRequest<IStandard<backInfo_video[]>, sendInfo_video>({
      url: 'videos',
      method: 'post',
      data: {
        page: this.page + 1,
        pageSize: 5
      }
    })
    console.log(JSON.stringify(resData.data.result))
    this.page += 1
    // 获取返回数据的长度,长度<=上面那个发送数据的pageSize
    const videoPoolLen = resData.data.result.length
    // 遍历 --> 创建基本格式 --> 提取部分字段 --> push进去
    for (let i = 0; i < videoPoolLen; i++) {
      // 查询视频作者的用户昵称
      //  封装的sendRequest方法,基于axios库,需要可查看我之前的帖子,或使用fetch等其它请求方法。
      const resUsername = await sendRequest<IStandard<backInfo_username>, sendInfo_username>({
        url: 'username',
        data: {
          id: resData.data.result[i].uploader_id
        },
        method: 'post'
      })
      const temPushVideoPoolInfo: VideoItem = {
        videoUrl: resData.data.result[i].url,
        imgUrl: resData.data.result[i].cover_url,
        title: resData.data.result[i].title,
        description: resData.data.result[i].description,
        uploaderId: resData.data.result[i].uploader_id,
        updatedTime: resData.data.result[i].updated_time,
        category: resData.data.result[i].category,
        commentCount: resData.data.result[i].comment_count,
        likeCount: resData.data.result[i].like_count,
        viewCount: resData.data.result[i].view_count,
        shareCount: resData.data.result[i].share_count,
        saveCount: resData.data.result[i].save_count,
        status: resData.data.result[i].status,
        userNickname: resUsername.data.result.username || '未知用户'
      }
      this.rawList.push(temPushVideoPoolInfo)
    }
  }

  // TODO:下拉刷新
  async refreshVideoList() {
    // 首先置空列表
    this.rawList = []
    // 然后重置page的值
    this.page = 0
    // 然后触发获取视频信息
    this.getVideoList()
  }

  async updateVideoList() {
    if (this.activeIndex + 1 === this.rawList.length) {
      this.getVideoList()
    }
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {

      // 视频的swiper部分
      Swiper() {
        ForEach(this.rawList, (item: VideoItem, index: number) => {
          PlayVideo({ item, index })
        })
      }
      .index($$this.activeIndex)
      .cachedCount(5)
      .loop(false)
      .indicator(false)
      .vertical(true)
      .width('100%')
      .height('100%')

      // 顶部的横向滚动栏
      Column() {
        Row() {
          Column() {
            Image($r('app.media.more'))
              .height(20)
              .fillColor(Color.White)
              .margin({ left: 10, right: 10 })
            Image($r('app.media.exchange'))
              .width(13)
              .fillColor('rgba(0.0.0.0)')
              .opacity(0)
          }

          Scroll(this.scroller) {
            Row() {
              ForEach(this.topList, (item: string, index) => {
                Column() {
                  Text(item)
                    .fontColor(Color.White)
                    .height(20)
                    .width('14%')
                    .textAlign(TextAlign.Center)
                  Column() {
                    Image($r('app.media.exchange'))
                      .width(12)
                      .fillColor(index === this.topList.length - 1 ? Color.White : 'rgba(0.0.0.0)')
                      .opacity(index === this.topList.length - 1 ? 1 : 0)
                  }
                  .width('14%')
                }
              })
            }
            .layoutWeight(1)
          }
          .scrollBar(BarState.Off)
          .scrollable(ScrollDirection.Horizontal)
          .layoutWeight(1)
          .onAppear(() => {
            this.scroller.scrollPage({
              next: true
            })
          })

          Column() {
            Image($r('app.media.search'))
              .height(20)
              .fillColor(Color.White)
              .margin({ right: 10, left: 10 })
            Image($r('app.media.exchange'))
              .width(13)
              .fillColor('rgba(0.0.0.0)')
              .opacity(0)
          }
        }
        .width('100%')
        .margin({ top: 20 })
      }
      .width('100%')
    }
    .width('100%')
    .height('100%')
  }
}

自定义组件 – 播放视频

@Component
struct PlayVideo {
  item: VideoItem = new VideoItem()
  index: number = -1
  @Consume
  @Watch("updateActiveIndex")
  activeIndex: number
  controller: VideoController = new VideoController()
  @State
  playIng: boolean = true

  updateActiveIndex() {
    if (this.activeIndex === this.index) {
      this.controller.start()
      this.playIng = true
    } else {
      this.controller.pause()
      this.playIng = false
    }
  }

  // 将后端返回的视频标签数组拼接一下
  openArray(cate: string[]) {
    let reArr: string = ''
    for (let i = 0; i < cate.length; i++) {
      reArr += ' #' + cate[i]
    }
    return reArr
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      // 主体思路分为两层,层叠布局。下层为视频swiper部分。
      // 上层叠加为信息展示区、按钮操作区、空白区通过controller绑定控制视频的播放暂停与快进等,或自定义其它操作

      // 视频层
      Column() {
        if (!this.playIng) {
          Image($r('app.media.play_circle_fill'))
            .width(60)
            .height(60)
            .fillColor(Color.Gray)
            .position({ x: '50%', y: '50%' })
            .translate({ x: '-50%', y: '-50%' })
            .zIndex(999)
        }
        Video({
          src: this.item.videoUrl,
          previewUri: this.item.imgUrl,
          controller: this.controller
        })
          .width('100%')
          .autoPlay(this.index === 0)
          .loop(true)
          .aspectRatio(1.8)
          .controls(false)
      }
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.Black)
      .width('100%')
      .height('100%')

      // 操作+展示层
      Column() {
        // 四行:text占位、操作主体区、相关搜索、进度条

        // text占位
        Text('')
          .width('100%')
          .height(53) // margin-top的20,文字20,exchange13

        // 操作主体区
        // 左右两个区域
        Row() {
          // 左Column:滑动点击区+视频信息区
          Column() {
            // 滑动点击区暂时Text占位,宽度100%,高度占满
            Text('')
              .width('100%')
              .layoutWeight(1)
              .onClick(() => {
                if (this.playIng) {
                  this.controller.pause()
                } else {
                  this.controller.start()
                }
                this.playIng = !this.playIng
              })

            // 视频信息区继续Column平铺
            Column({ space: 10 }) {
              // 弹幕开关
              Image($r('app.media.danmu'))
                .width(25)
                .fillColor(Color.White)
                .margin({ left: 10 })

              // 点击推荐
              Row({ space: 5 }) {
                Image($r('app.media.good_filling'))
                  .width(15)
                  .fillColor(Color.White)
                  .margin({ left: 5 })
                Text('点击推荐')
                  .fontSize(12)
                  .fontColor(Color.White)
                Image($r('app.media.right_arrow'))
                  .width(15)
                  .fillColor(Color.White)
                  .margin({ right: 5 })
              }
              .height(25)
              .margin({ left: 10 })
              .borderRadius(5)
              .backgroundColor('rgba(100,100,100,0.3)')

              // 用户名
              Text('@' + this.item.userNickname)
                .margin({ left: 10 })
                .fontColor(Color.White)
                .fontWeight(700)
                .fontSize(18)

              // 视频简介(两行可展开)
              Text(this.item.description + this.openArray(this.item.category))
                .fontSize(14)
                .maxLines(2)
                .margin({ left: 10, bottom: 10 })
                .fontColor(Color.White)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
            }
            .width('100%')
            .alignItems(HorizontalAlign.Start)
          }
          .width('85%')

          // 右Column:加速播放区+按钮操作区
          Column() {
            // 加速播放区域,暂时用text占位
            Text('')
              .width('100%')
              .layoutWeight(1)

            // 按钮操作区域
            Column({ space: 5 }) {
              Stack({ alignContent: Alignment.Bottom }) {
                Image($r('app.media.user_normal'))
                  .width('100%')
                  .border({ color: Color.White, width: 2 })
                  .borderRadius(100)
                  .fillColor(Color.White)
                  .backgroundColor(Color.Gray)
                Image($r('app.media.plus'))
                  .width(18)
                  .padding(5)
                  .borderRadius(10)
                  .fillColor(Color.White)
                  .backgroundColor(Color.Red)
                  .offset({ x: 0, y: 9 })
              }
              .width('70%')
              .margin({ bottom: 5 })

              Column() {
                Image($r('app.media.like_fill'))
                  .width('100%')
                  .fillColor(Color.White)
                Text(this.item.likeCount.toString())
                  .width('100%')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
              }
              .width('60%')

              Column() {
                Image($r('app.media.interactive_fill'))
                  .width('100%')
                  .fillColor(Color.White)
                Text(this.item.commentCount.toString())
                  .width('100%')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
              }
              .width('60%')

              Column() {
                Image($r('app.media.collection_fill'))
                  .width('100%')
                  .fillColor(Color.White)
                Text(this.item.saveCount.toString())
                  .width('100%')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
              }
              .width('60%')

              Column() {
                Image($r('app.media.share_fill'))
                  .width('100%')
                  .fillColor(Color.White)
                Text(this.item.shareCount.toString())
                  .width('100%')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
              }
              .width('60%')

              Image($r('app.media.music'))
                .width('70%')
                .borderRadius(100)
                .fillColor(Color.White)
                .backgroundColor(Color.Grey)
            }
            .margin({ bottom: 10 })
          }
          .width('15%')
        }
        .width('100%')
        .layoutWeight(1)

        // 相关搜索
        Row() {
          Image($r('app.media.search'))
            .width(15)
            .margin({ left: 10, right: 10 })
            .fillColor(Color.White)
          Text('相关搜索 · ')
            .fontColor(Color.White)
            .fontSize(15)
          Text('${搜索类目}')
            .fontColor(Color.White)
            .fontSize(15)
          Text('') // 占位
            .layoutWeight(1)
          Image($r('app.media.right_arrow'))
            .width(20)
            .margin({ right: 5 })
            .fillColor(Color.White)
        }
        .height(30)
        .width('100%')
        .backgroundColor('rgba(100,100,100,0.3)')

        // 进度条,暂时用分割线占位
        Divider()
          .width('100%')
          .strokeWidth(2)
          .backgroundColor(Color.Pink)
      }
    }
    .height('100%')
    .width('100%')
  }
}
class VideoItem {
  title: string = ''
  description: string = ''
  uploaderId: number = 0
  updatedTime: string = ''
  videoUrl: string = ''
  category: string[] = []
  commentCount: number = 0
  likeCount: number = 0
  viewCount: number = 0
  shareCount: number = 0
  saveCount: number = 0
  status: number = 0
  imgUrl: string = ''
  userNickname?: string = ''
}

更多关于HarmonyOS鸿蒙Next中一种短视频软件首页的设计思路的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next中设计短视频软件首页时,可以采用以下思路:

  1. 界面布局:首页通常采用全屏展示,视频内容占据主要区域。顶部可以设置导航栏,包括搜索、消息、个人中心等功能入口。底部可以设置标签栏,用于快速切换不同分类或功能模块。

  2. 视频流展示:视频流采用瀑布流或垂直滚动方式,支持自动播放和手动滑动切换。每个视频卡片可以包含封面、标题、作者信息、点赞、评论等交互元素。

  3. 交互设计:支持上下滑动切换视频,左右滑动切换分类或功能模块。双击屏幕可点赞,长按可弹出更多操作选项,如分享、收藏、举报等。

  4. 个性化推荐:根据用户行为和偏好,动态调整视频推荐内容。首页可以设置“推荐”、“关注”、“热门”等不同分类,满足用户多样化需求。

  5. 实时更新:首页内容需实时更新,确保用户每次打开都能看到最新视频。可以通过推送通知或下拉刷新方式,提醒用户有新内容。

  6. 性能优化:由于视频加载和播放对性能要求较高,需优化视频缓存、预加载和播放流畅度,确保用户体验。

  7. 适配与兼容:确保首页设计在不同设备上都能良好显示和操作,包括手机、平板、智慧屏等。

通过以上设计思路,可以在HarmonyOS鸿蒙Next中实现一个功能完善、交互流畅、用户体验良好的短视频软件首页。

更多关于HarmonyOS鸿蒙Next中一种短视频软件首页的设计思路的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,短视频软件首页的设计应注重简洁与高效。采用瀑布流布局,支持智能推荐与个性化内容展示,提升用户体验。利用鸿蒙的分布式能力,实现跨设备无缝切换观看。同时,集成高效的视频加载与播放技术,确保流畅性。此外,通过鸿蒙的原子化服务,用户可快速分享、评论,增强社交互动。整体设计应遵循鸿蒙的设计规范,确保一致性与易用性。

回到顶部