在HarmonyOS NEXT开发环境中,可以使用多种组件和库来构建丰富且交互友好的应用。本文将展示如何使用HarmonyOS NEXT框架和
nutpi/axios
库,从零开始实现一个简单的影视APP的首页,主要关注最近上映电影的滚动展示及加载更多功能的实现。
开源项目地址:https://atomgit.com/csdn-qq8864/hmmovie
好的作品是需要不断打磨的,在你的学习和体验过程中有任何问题,欢迎到我的开源项目代码仓下面提交issue,持续优化。
安装nutpi/axios
首先,需要在项目中安装nutpi/axios
库。
ohpm install @ohos/axios
实现最近上映电影接口
使用nutpi/axios
库来实现最近上映电影的接口请求。接口请求如下:
import {axiosClient,HttpPromise} from '../../utils/axiosClient';
import { HotMovieReq, MovieRespData, SwiperData } from '../bean/ApiTypes';// 1.获取轮播图接口
export const getSwiperData = (): HttpPromise<SwiperData> => axiosClient.get({url:'/swiperdata'});// 2.获取即将上映影视接口
export const getSoonMovie = (start:number,count:number): HttpPromise<MovieRespData> => axiosClient.post({url:'/soonmovie',data: { start:start,count:count }});// 3.获取热门影视接口
export const getHotMovie = (req:HotMovieReq): HttpPromise<MovieRespData> => axiosClient.post({url:'/hotmovie',data:req});
代码讲解
- 创建axios客户端:创建了一个axios客户端
axiosClient
,并设置了基础URL和请求超时时间。 - 定义接口函数:
getSoonMovies
函数向接口发送GET请求以获取最近上映的电影数据,并返回一个Promise对象。
实现首页组件
接下来,将实现首页组件,包括最近上映电影的滚动展示和加载更多功能。
import { getSoonMovies } from '../../common/api/movie';
import { MovieItem } from '../../common/bean/MovieItem';
import { Log } from '../../utils/logutil';
import { NavPathStack } from '@ohos.router';
import { BusinessError } from '@kit.BasicServicesKit';@Builder
export function HomePageBuilder() {HomePage()
}@Component
struct HomePage {pageStack: NavPathStack = new NavPathStack()@State soonMvList: MovieItem[] = []// 组件生命周期aboutToAppear() {Log.info('HomePage aboutToAppear');this.fetchSoonMovies();}// 获取最近上映电影数据fetchSoonMovies() {getSoonMovies().then((res) => {Log.debug(res.data.message)Log.debug("request", "res.data.code:%{public}d", res.data.code)if (res.data.code == 0) {this.soonMvList = res.data.data}}).catch((err: BusinessError) => {Log.debug("request", "err.data.code:%d", err.code)Log.debug("request", err.message)});}build() {Column({ space: 0 }) {Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('即将上映').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black).onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", { types: 1 }).catch((e: Error) => {console.log(`catch exception: ${JSON.stringify(e)}`)}).then(() => {// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.soonMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id: item.id }).catch((e: Error) => {console.log(`catch exception: ${JSON.stringify(e)}`)}).then(() => {// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem) => itm.id)}}.scrollable(ScrollDirection.Horizontal)}.width('100%').height('100%')}
}
代码讲解
- 导入模块:导入了之前定义的
getSoonMovies
函数,以及一些其他必要的模块。 - 定义组件状态:
soonMvList
:用于存储最近上映电影的数据。
- 组件生命周期:
aboutToAppear
:组件即将出现在页面时执行的日志记录,并调用fetchSoonMovies
函数获取数据。
- 构建页面:
Column
:垂直布局容器。Flex
:弹性布局容器,用于布局“即将上映”和“加载更多”按钮。Text('即将上映')
:显示“即将上映”标题。Text('加载更多 >>')
:显示“加载更多”按钮,并设置点击事件,点击时导航到加载更多页面。
Scroll
:滚动容器,设置为水平滚动。Row
:水平布局容器,用于放置电影项。ForEach
:遍历soonMvList
数组,为每个电影项创建一个Column
。Column
:垂直布局容器,用于放置电影的图片和标题。Image
:显示电影封面图片,并设置点击事件,点击时导航到电影详情页面。Text
:显示电影标题,并设置样式。
配置路由
为了实现页面跳转,需要在entry/src/main/resources/base/profile
路径下的route_map.json
文件中配置路由。
{"routerMap": [{"name": "HomePage","pageSourceFile": "src/main/ets/pages/home/Home.ets","buildFunction": "HomePageBuilder","data": {"description": "this is HomePage"}},{"name": "MovieMorePage","pageSourceFile": "src/main/ets/pages/home/MovieMore.ets","buildFunction": "MovieMorePageBuilder","data": {"description": "this is MovieMorePage"}},{"name": "MovieDetailPage","pageSourceFile": "src/main/ets/pages/home/Detail.ets","buildFunction": "MovieDetailPageBuilder","data": {"description": "this is MovieDetailPage"}}]
}
代码讲解
- 配置路由:
HomePage
:配置首页的路由信息。MovieMorePage
:配置加载更多页面的路由信息。MovieDetailPage
:配置电影详情页面的路由信息。
涉及到的常用组件
Flex
Flex
组件用于创建弹性布局容器,可以控制子组件的排列方式和对齐方式。
Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween
}) {Text('即将上映').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black)
}
Scroll
Scroll
组件用于创建可滚动的容器,可以设置滚动方向为水平或垂直。
Scroll() {Row({ space: 5 }) {ForEach(this.soonMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5)}}, (itm: MovieItem) => itm.id)}
}.scrollable(ScrollDirection.Horizontal)
ForEach
ForEach
组件用于遍历数组并为每个元素生成一个子组件。
ForEach(this.soonMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5)}
}, (itm: MovieItem) => itm.id)
Image
Image
组件用于显示图片,可以设置图片的显示方式、大小和形状。
Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5)
Text
Text
组件用于显示文本,可以设置文本的大小、颜色、对齐方式等。
Text(item.title).fontSize(14).fontWeight(FontWeight.Bold).padding(10)
Column
和 Row
Column
和Row
组件分别用于创建垂直和水平布局容器。
Column({ space: 0 }) {// 子组件
}Row({ space: 5 }) {// 子组件
}
onClick
onClick
事件用于处理组件的点击事件。
Text('加载更多 >>').fontSize(14).fontColor(Color.Black).onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", { types: 1 }).catch((e: Error) => {console.log(`catch exception: ${JSON.stringify(e)}`)}).then(() => {// 跳转成功});})
总结
通过本文,展示了如何使用HarmonyOS NEXT框架和nutpi/axios
库来实现一个简单的影视APP的首页,包括最近上映电影的滚动展示和加载更多功能。nutpi/axios
库的使用大大简化了网络请求的操作,使代码更加简洁易读。同时,还介绍了涉及到的常用组件的使用方法。
希望这篇文章对你有所帮助,让你在开发HarmonyOS NEXT应用时更加得心应手。如果你有任何问题或建议,欢迎在评论区留言交流!
参考代码
以下是完整的HomePage
组件代码:
import { Log } from '../../utils/logutil';
import { BusinessError } from '@kit.BasicServicesKit';
import { router } from '@kit.ArkUI';
import { MovieItem, SwiperItem } from '../../common/bean/ApiTypes';
import { getHotMovie, getHotTv,getMvTop250,getNewMovie,getPiaoMovie,getSoonMovie, getSwiperData, getUsHotMv } from '../../common/api/movie';
import { PiaoFangRespData } from '../../common/bean/PiaoFangResp';class BasicDataSource<T> implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: T[] = [];totalCount(): number {return this.originDataArray.length;}getData(index: number): T {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {this.listeners.slice(pos, 1);}}// 通知LazyForEach组件需要重新重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}
}class SwiperDataSource<T> extends BasicDataSource<T> {private dataArray: T[] = [];totalCount(): number {return this.dataArray.length;}getData(index: number): T {return this.dataArray[index];}// 在列表末尾添加数据并通知监听器pushData(data: T): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}// 重载数据reloadData(): void {// 不会引起状态变化this.dataArray = [];// 必须通过DataChangeListener来更新this.notifyDataReload();}
}@Component
export default struct Home {@Consume pageStack: NavPathStackprivate swiperController: SwiperController = new SwiperController()private swiperData: SwiperDataSource<SwiperItem> = new SwiperDataSource()@State soonMvList:MovieItem[]=[]@State hotMvList:MovieItem[]=[]@State hotTvList:MovieItem[]=[]@State usMvList:MovieItem[]=[]@State topMvList:MovieItem[]=[]@State piaoList:PiaoFangRespData[]=[]// 组件生命周期aboutToAppear() {Log.info('Home aboutToAppear');getSwiperData().then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)for (const itm of res.data.data) {this.swiperData.pushData(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getPiaoMovie().then((res) => {Log.debug(res.data.message)for (const itm of res.data.data) {this.piaoList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getSoonMovie(1,10).then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)for (const itm of res.data.data) {this.soonMvList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getHotMovie({start:1,count:10,city:'郑州'}).then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)for (const itm of res.data.data) {this.hotMvList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getHotTv(1,10).then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)for (const itm of res.data.data) {this.hotTvList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getUsHotMv(1,10).then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)for (const itm of res.data.data) {this.usMvList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});getMvTop250(1,10).then((res) => {Log.debug(res.data.message)for (const itm of res.data.data) {this.topMvList.push(itm)}}).catch((err: BusinessError) => {Log.debug("request","err.data.code:%d",err.code)Log.debug("request",err.message)});}// 组件生命周期aboutToDisappear() {Log.info('Home aboutToDisappear');}build() {Scroll() {Column({ space: 0 }) {//titleFlex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}){Blank().width(40)Text('爱影家').fontSize(26).fontWeight(FontWeight.Bold)Image($r('app.media.search')).width(42).height(42).padding({bottom:8}).onClick(() => {this.pageStack.pushDestinationByName("SearchPage", { }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10).width('100%').height(50)// 轮播图Swiper(this.swiperController) {LazyForEach(this.swiperData, (item: SwiperItem) => {Stack({ alignContent: Alignment.Center }) {Image(item.imageUrl).width('100%').height(180).zIndex(1).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})// 显示轮播图标题Text(item.title).padding(5).margin({ top: 135 }).width('100%').height(60).textAlign(TextAlign.Center).maxLines(2).textOverflow({ overflow: TextOverflow.Clip }).fontSize(22).fontColor(Color.White).opacity(100)// 设置标题的透明度 不透明度设为100%,表示完全不透明.backgroundColor('#808080AA')// 背景颜色设为透明.zIndex(2).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}}, (item: SwiperItem) => item.id)}.cachedCount(2).index(1).autoPlay(true).interval(4000).loop(true).indicatorInteractive(true).duration(1000).itemSpace(0).curve(Curve.Linear).onChange((index: number) => {console.info(index.toString())}).onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {console.info("index: " + index)console.info("current offset: " + extraInfo.currentOffset)}).height(180) // 设置高度Text('今日票房').fontWeight(FontWeight.Bold).padding(10).alignSelf(ItemAlign.Start)Scroll() {Row({ space: 5 }) {ForEach(this.piaoList, (item: PiaoFangRespData,idx) => {Column({ space: 2 }) {Text(idx.toString()).fontSize(20).fontColor(Color.Orange)Text(item.name).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).fontSize(14).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Center).padding(5)Text(item.release_date).fontSize(12).fontColor(Color.White).alignSelf(ItemAlign.Center)Text('票房:'+item.box_million).fontSize(12).fontColor(Color.White).alignSelf(ItemAlign.Center)Text('占比:'+item.share_box).fontSize(12).fontColor(Color.White).alignSelf(ItemAlign.Center)}.width(120).height(120).backgroundColor('rgba(85, 170, 255, 0.60)').borderRadius(5).justifyContent(FlexAlign.Center)}, (itm: PiaoFangRespData, idx) => itm.name)}}.scrollable(ScrollDirection.Horizontal)Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('即将上映').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", {types:1}).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.soonMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem, idx) => itm.id)}}.scrollable(ScrollDirection.Horizontal)Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('热映电影').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", {types:2}).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.hotMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem, idx) => itm.id)}}.scrollable(ScrollDirection.Horizontal)// Flex({// direction: FlexDirection.Row,// justifyContent: FlexAlign.SpaceBetween,// alignContent: FlexAlign.SpaceBetween// }) {// Text('热门资讯').fontWeight(FontWeight.Bold)// Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {//// })// }.padding(10)Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('热门剧集').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", {types:3}).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.hotTvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem, idx) => itm.id)}}.scrollable(ScrollDirection.Horizontal)Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('豆瓣排行榜').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", {types:4}).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.topMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem, idx) => itm.id)}}.scrollable(ScrollDirection.Horizontal)Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('北美票房榜').fontWeight(FontWeight.Bold)Text('加载更多 >>').fontSize(14).fontColor(Color.Black) .onClick(() => {this.pageStack.pushDestinationByName("MovieMorePage", {types:5}).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})}.padding(10)Scroll() {Row({ space: 5 }) {ForEach(this.usMvList, (item: MovieItem) => {Column({ space: 0 }) {Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5).onClick(() => {this.pageStack.pushDestinationByName("MovieDetailPage", { id:item.id }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})Text(item.title).alignSelf(ItemAlign.Center).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(100).fontSize(14).fontWeight(FontWeight.Bold).padding(10)}.justifyContent(FlexAlign.Center)}, (itm: MovieItem, idx) => itm.id)}}.scrollable(ScrollDirection.Horizontal)}.width('100%')}.width('100%').height('100%').scrollable(ScrollDirection.Vertical)}
}
作者介绍
作者:csdn猫哥
原文链接:https://blog.csdn.net/yyz_1987
团队介绍
坚果派团队由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉等相关内容,团队成员聚集在北京、上海、南京、深圳、广州、宁夏等地,目前已开发鸿蒙原生应用和三方库60+,欢迎交流。
版权声明
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。