HarmonyOS 鸿蒙Next应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现)

发布于 1周前 作者 gougou168 来自 鸿蒙OS

HarmonyOS 鸿蒙Next应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现) 在上一篇文章中,我们完成了电影列表页的开发。接下来,我们将进入电影详情页的设计实现阶段。这个页面将展示电影的详细信息,包括电影海报、评分、简介以及相关影人等。我们将使用 HarmonyOS 提供的常用组件,并结合第三方库 nutpi/axios 来实现网络请求。下面,我们一步步地来看如何实现电影详情页。

项目开源地址:https://atomgit.com/csdn-qq8864/hmmovie

获取电影详情接口

首先,我们需要定义一个用于获取电影详情的接口。这里我们使用 axiosClient.post 方法来实现,代码如下:

export const getDetailMv = (id: string): HttpPromise<DetailMvResp> => axiosClient.post({ url: '/detailmovie', data: { id: id } });

电影详情接口的具体信息如下:

POST http://120.27.146.247:8000/api/v1/detailmovie
Content-Type: application/json

{
    "id": "1292052"
}

电影详情页的组件使用

在电影详情页中,我们将使用 BadgeSymbolSpanButtonRating 等组件来展示电影的详细信息。

  • Badge 组件

    Badge 组件用于在某个组件的右上角显示一个标记,通常用于展示某种数量或状态。在电影详情页中,我们将使用 Badge 组件来展示“想看”和“看过”的数量。

  • SymbolSpan 组件

    SymbolSpan 组件用于在文本中嵌入图标。在电影详情页中,我们将使用 SymbolSpan 组件来展示“想看”和“看过”的图标。

  • Button 组件

    Button 组件用于创建按钮,用户可以通过点击按钮来执行特定的操作。在电影详情页中,我们将使用 Button 组件来创建“播放”按钮,用户点击后可以跳转到视频播放页面。

  • Rating 组件

    Rating 组件用于展示评分。在电影详情页中,我们将使用 Rating 组件来展示电影的豆瓣评分。

电影详情页的实现

下面,我们将展示电影详情页的具体实现代码:

@Builder
export function MovieDetailPageBuilder() {
  Detail()
}

@Component
struct Detail {
  pageStack: NavPathStack = new NavPathStack()
  private uid = ''
  @State detailData: DetailMvResp | null = null;
  private srcData: MvSourceResp | null = null;

  private description: string = ''
  private isToggle = false
  @State toggleText: string = ''
  @State toggleBtn: string = '展开'

  build() {
    NavDestination() {
      Column({ space: 0 }) {
        Row() {
          Image(this.detailData?.images).objectFit(ImageFit.Auto).width(120).borderRadius(5)
          Column({ space: 8 }) {
            Text(this.detailData?.title).fontSize(18)
            Text(this.detailData?.year + " " + this.detailData?.genre).fontSize(14)

            Row() {
              Badge({
                count: this.detailData?.wish_count,
                maxCount: 10000,
                position: BadgePosition.RightTop,
                style: { badgeSize: 22, badgeColor: '#fffab52a' }
              }) {
                Row() {
                  Text() {
                    SymbolSpan($r('sys.symbol.heart'))
                      .fontWeight(FontWeight.Lighter)
                      .fontSize(32)
                      .fontColor(['#fffab52a'])
                  }
                  Text('想看')
                }.backgroundColor('#f8f4f5').borderRadius(5).padding(5)
              }.padding(8)

              Blank(10).width(40)
              Badge({
                count: this.detailData?.reviews_count,
                maxCount: 10000,
                position: BadgePosition.RightTop,
                style: { badgeSize: 22, badgeColor: '#fffab52a' }
              }) {
                Row() {
                  Text() {
                    SymbolSpan($r('sys.symbol.star'))
                      .fontWeight(FontWeight.Lighter)
                      .fontSize(32)
                      .fontColor(['#fffab52a'])
                  }
                  Text('看过')
                }.backgroundColor('#f8f4f5').borderRadius(5).padding(5)
              }.padding(8)
            }

            Button('播放', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL })
              .borderRadius(8)
              .borderColor('#fffab52a')
              .fontColor('#fffab52a')
              .width(100)
              .height(35)
              .onClick(() => {
                console.info('Button onClick')
                if (this.srcData != null) {
                  this.pageStack.pushDestinationByName("VideoPlayerPage", { item: { video: this.srcData.urls[0], tvurls: this.srcData.tvurls, title: this.srcData.title, desc: this.detailData?.summary } }).catch((e: Error) => {
                    // 跳转失败,会返回错误码及错误信息
                    console.log(`catch exception: ${JSON.stringify(e)}`)
                  }).then(() => {
                    // 跳转成功

                  });
                } else {
                  promptAction.showToast({ message: '暂无资源' })
                }
              })
          }.alignItems(HorizontalAlign.Start) // 水平方向靠左对齐
            .justifyContent(FlexAlign.Start)   // 垂直方向靠上对齐
            .padding(10)
        }.height(160).width('100%')

        Row() {
          Text('豆瓣评分').fontSize(16).padding(5)
          Rating({ rating: (this.detailData?.rate ?? 0) / 2, indicator: true })
            .stars(5)
            .stepSize(0.5).height(28)

          Text(this.detailData?.rate.toString()).fontColor('#fffab52a').fontWeight(FontWeight.Bold).fontSize(36).padding(5)
        }.width('100%').height(80).borderRadius(5).backgroundColor('#f8f4f5').margin(20)

        Text('简介').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start)

        Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start)
        Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {
          this.isToggle = !this.isToggle
          if (this.isToggle) {
            this.toggleBtn = '收起'
            this.toggleText = this.description
          } else {
            this.toggleBtn = '展开'
            this.toggleText = this.description.substring(0, 100) + '...'
          }
        })

        Text('影人').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start)
        Scroll() {
          Row({ space: 5 }) {
            ForEach(this.detailData?.cast, (item: DetailMvRespCast) => {
              Column({ space: 0 }) {
                Image(item.cover).objectFit(ImageFit.Auto).height(120).borderRadius(5)
                  .onClick(() => {

                  })

                Text(item.name)
                  .alignSelf(ItemAlign.Center)
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                  .fontSize(14).padding(10)
              }.justifyContent(FlexAlign.Center)

            }, (itm: DetailMvRespCast, idx) => itm.id)
          }
        }.scrollable(ScrollDirection.Horizontal)

      }.padding({ left: 10, right: 10 })
    }.title("电影详情")
      .width('100%')
      .height('100%')
      .onReady(ctx => {
        this.pageStack = ctx.pathStack
        //从上个页面拿参数
        this.pageStack.getParamByName("MovieDetailPage")
        interface params {
          id: string;
        }
        let par = ctx.pathInfo.param as params
        Log.debug("par:%s", par.id)
        this.uid = par.id
      })
      .onShown(() => {
        console.info('Detail onShown');
        getDetailMv(this.uid).then(res => {
          Log.debug(res.data.message)
          Log.debug("request", "res.data.code:%{public}d", res.data.code)
          this.detailData = res.data
          this.description = this.detailData.summary
          this.toggleText = this.description.substring(0, 100) + '...'
        }).catch(err => {
          Log.debug("request", "err.data.code:%d", err.code)
          Log.debug("request", err.message)
        });

        getMovieSrc(this.uid).then(res => {
          Log.debug(res.data.message)
          Log.debug("request", "res.data.code:%{public}d", res.data.code)
          if (res.data.code == 0) {
            this.srcData = res.data
          }
        }).catch(err => {
          Log.debug("request", "err.data.code:%d", err.code)
          Log.debug("request", err.message)
        });
      })
  }
}

折叠效果的实现

在电影详情页中,对于电影的简介,我们使用了折叠效果,即默认只显示部分简介内容,用户点击“展开”按钮后可以查看完整简介。这个效果的实现主要通过控制 Text 组件的显示内容来实现。具体代码如下:

Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start)
Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {
  this.isToggle = !this.isToggle
  if (this.isToggle) {
    this.toggleBtn = '收起'
    this.toggleText = this.description
  } else {
    this.toggleBtn = '展开'
    this.toggleText = this.description.substring(0, 100) + '...'
  }
})

通过上述代码,我们实现了电影简介内容的折叠效果。

总结

在本文中,我们完成了电影详情页的设计与实现。主要使用了 BadgeSymbolSpanButtonRating 等组件,并结合 nutpi/axios 库来实现网络请求。通过这些技术,我们可以快速地开发出功能丰富、用户体验良好的影视应用。希望本文对你有所帮助。

作者介绍

作者:csdn猫哥

原文链接:https://blog.csdn.net/yyz_1987

团队介绍

坚果派团队由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉等相关内容,团队成员聚集在北京、上海、南京、深圳、广州、宁夏等地,目前已开发鸿蒙原生应用和三方库60+,欢迎交流。

版权声明

本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

其他资源

官方系统图标资源:https://developer.huawei.com/consumer/cn/doc/design-guides/system-icons-0000001929854962

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-symbolSpan.md

https://developer.huawei.com/consumer/cn/design/harmonyos-symbol/


更多关于HarmonyOS 鸿蒙Next应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next应用开发中,针对“从零实现一影视APP (五、电影详情页的设计实现)”这一主题,以下是关于电影详情页设计实现的简要回答:

电影详情页是影视APP中的关键页面,用于展示电影的详细信息。在鸿蒙系统中,你可以利用ArkUI(eTS)来构建这一页面。

首先,设计页面布局。使用<Stack><Column><Row>等布局容器来组织页面元素,如电影海报、标题、导演、演员列表、剧情简介等。确保布局合理,美观且易于用户交互。

其次,加载电影数据。通过网络请求(如HTTP)从服务器获取电影详情数据,并绑定到页面元素上。鸿蒙系统提供了丰富的网络请求API,方便你实现这一功能。

最后,实现用户交互。为页面元素添加点击、滑动等事件监听器,响应用户操作。例如,点击播放按钮可以跳转到播放页面,点击演员名称可以查看演员详情等。

在实现过程中,注意遵循鸿蒙系统的开发规范和最佳实践,确保应用的质量和性能。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部