HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现)
在上一篇文章中,我们完成了电影列表页的开发。接下来,我们将进入电影详情页的设计实现阶段。这个页面将展示电影的详细信息,包括电影海报、评分、简介以及相关影人等。我们将使用 HarmonyOS 提供的常用组件,并结合第三方库
nutpi/axios
来实现网络请求。下面,我们一步步地来看如何实现电影详情页。
获取电影详情接口
首先,我们需要定义一个用于获取电影详情的接口。这里我们使用 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"
}
电影详情页的组件使用
在电影详情页中,我们将使用 Badge
、SymbolSpan
、Button
、Rating
等组件来展示电影的详细信息。
-
Badge 组件
Badge
组件用于在某个组件的右上角显示一个标记,通常用于展示某种数量或状态。在电影详情页中,我们将使用Badge
组件来展示“想看”和“看过”的数量。 -
SymbolSpan 组件
SymbolSpan
组件用于在文本中嵌入图标。在电影详情页中,我们将使用SymbolSpan
组件来展示“想看”和“看过”的图标。 -
Button 组件
Button
组件用于创建按钮,用户可以通过点击按钮来执行特定的操作。在电影详情页中,我们将使用Button
组件来创建“播放”按钮,用户点击后可以跳转到视频播放页面。 -
Rating 组件
Rating
组件用于展示评分。在电影详情页中,我们将使用Rating
组件来展示电影的豆瓣评分。
电影详情页的实现
下面,我们将展示电影详情页的具体实现代码:
import { getDetailMv, getMovieSrc } from "../../common/api/movie"
import { Log } from "../../utils/logutil"
import { BusinessError } from "@kit.BasicServicesKit"
import { DetailMvResp, DetailMvRespCast } from "../../common/bean/DetailMvResp"
import { LengthMetrics, promptAction } from "@kit.ArkUI"
import { MvSourceResp } from "../../common/bean/MvSourceResp"
@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: BusinessError) => {
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: BusinessError) => {
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) + '...'
}
})
通过上述代码,我们实现了电影简介内容的折叠效果。
总结
在本文中,我们完成了电影详情页的设计与实现。主要使用了 Badge
、SymbolSpan
、Button
、Rating
等组件,并结合 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://developer.huawei.com/consumer/cn/design/harmonyos-symbol/
- 3回答
- 3粉丝
- 5关注
- HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (三、影视搜索页功能实现)
- HarmonyOS NEXT应用开发边学边玩,从零实现一影视APP(四、最近上映电影滚动展示及加载更多的实现)
- HarmonyOS NEXT应用开发实战(一):边学边玩,从零开发一款影视APP
- HarmonyOS NEXT边学边玩,从零开发一款影视APP(二、首页轮播图懒加载的实现)
- HarmonyOS应用开发实战:半天实现知乎日报项目(八、知乎日报详情页的实现)
- 鸿蒙HarmonyOS从零实现类微信app基础界面搭建
- HarmonyOS NEXT 应用开发实战:音乐播放器的完整实现
- 从零到智能:打造智能灯控应用
- 【HarmonyOS NEXT】权限申请及应用设置页跳转
- 【HarmonyOS NEXT】权限申请及应用设置页跳转
- HarmonyOS应用开发实战:半天实现知乎日报项目( 五、组件导航Navigation使用详解)
- HarmonyOS NEXT实战:元服务与应用 APP 发布应用市场的详细步骤与流程
- 【HarmonyOS NEXT】Tabs组件实现类微信(可滑动的)tabBar页签切换页面功能
- HarmonyOS NEXT:模块化项目 ——修改应用图标+启动页等
- HarmonyOS NEXT模块化设计实践:打造简洁高效的登录注册页面