HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (四、最近上映电影滚动展示及加载更多的实现)

在HarmonyOS NEXT开发环境中,我们可以使用多种组件和库来构建丰富且交互友好的应用。本文将展示如何使用HarmonyOS NEXT框架和nutpi/axios库,从零开始实现一个简单的影视APP的首页,主要关注最近上映电影的滚动展示及加载更多功能的实现。

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

安装nutpi/axios

首先,我们需要在项目中安装nutpi/axios库。

bash 复制代码
ohpm install @ohos/axios

实现最近上映电影接口

我们使用nutpi/axios库来实现最近上映电影的接口请求。接口请求如下:

typescript 复制代码
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});

代码讲解

  1. 创建axios客户端 :我们创建了一个axios客户端axiosClient,并设置了基础URL和请求超时时间。
  2. 定义接口函数getSoonMovies函数向接口发送GET请求以获取最近上映的电影数据,并返回一个Promise对象。

实现首页组件

接下来,我们将实现首页组件,包括最近上映电影的滚动展示和加载更多功能。

typescript 复制代码
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%')
  }
}

代码讲解

  1. 导入模块 :我们导入了之前定义的getSoonMovies函数,以及一些其他必要的模块。
  2. 定义组件状态
    • soonMvList:用于存储最近上映电影的数据。
  3. 组件生命周期
    • aboutToAppear:组件即将出现在页面时执行的日志记录,并调用fetchSoonMovies函数获取数据。
  4. 构建页面
    • Column:垂直布局容器。
    • Flex:弹性布局容器,用于布局"即将上映"和"加载更多"按钮。
      • Text('即将上映'):显示"即将上映"标题。
      • Text('加载更多 >>'):显示"加载更多"按钮,并设置点击事件,点击时导航到加载更多页面。
    • Scroll:滚动容器,设置为水平滚动。
      • Row:水平布局容器,用于放置电影项。
      • ForEach:遍历soonMvList数组,为每个电影项创建一个Column
        • Column:垂直布局容器,用于放置电影的图片和标题。
          • Image:显示电影封面图片,并设置点击事件,点击时导航到电影详情页面。
          • Text:显示电影标题,并设置样式。

配置路由

为了实现页面跳转,我们需要在entry/src/main/resources/base/profile路径下的route_map.json文件中配置路由。

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"
      }
    }
  ]
}

代码讲解

  1. 配置路由
    • HomePage:配置首页的路由信息。
    • MovieMorePage:配置加载更多页面的路由信息。
    • MovieDetailPage:配置电影详情页面的路由信息。

涉及到的常用组件

Flex

Flex组件用于创建弹性布局容器,可以控制子组件的排列方式和对齐方式。

typescript 复制代码
Flex({
  direction: FlexDirection.Row,
  justifyContent: FlexAlign.SpaceBetween,
  alignContent: FlexAlign.SpaceBetween
}) {
  Text('即将上映').fontWeight(FontWeight.Bold)
  Text('加载更多 >>').fontSize(14).fontColor(Color.Black)
}

Scroll

Scroll组件用于创建可滚动的容器,可以设置滚动方向为水平或垂直。

typescript 复制代码
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组件用于遍历数组并为每个元素生成一个子组件。

typescript 复制代码
ForEach(this.soonMvList, (item: MovieItem) => {
  Column({ space: 0 }) {
    Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5)
  }
}, (itm: MovieItem) => itm.id)

Image

Image组件用于显示图片,可以设置图片的显示方式、大小和形状。

typescript 复制代码
Image(item.cover).objectFit(ImageFit.Auto).height(160).borderRadius(5)

Text

Text组件用于显示文本,可以设置文本的大小、颜色、对齐方式等。

typescript 复制代码
Text(item.title).fontSize(14).fontWeight(FontWeight.Bold).padding(10)

ColumnRow

ColumnRow组件分别用于创建垂直和水平布局容器。

typescript 复制代码
Column({ space: 0 }) {
  // 子组件
}

Row({ space: 5 }) {
  // 子组件
}

onClick

onClick事件用于处理组件的点击事件。

typescript 复制代码
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组件代码:

typescript 复制代码
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: NavPathStack
  private 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 }) {
          //title
          Flex({
            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)
    }
}
相关推荐
小冷爱学习!5 小时前
华为动态路由-OSPF-完全末梢区域
服务器·网络·华为
2501_904447745 小时前
华为发力中端,上半年nova14下半年nova15,大力普及原生鸿蒙
华为·智能手机·django·scikit-learn·pygame
MarkHD6 小时前
第十八天 WebView深度优化指南
华为·harmonyos
塞尔维亚大汉7 小时前
OpenHarmony(鸿蒙南向)——平台驱动开发【MIPI CSI】
harmonyos·领域驱动设计
别说我什么都不会7 小时前
鸿蒙轻内核M核源码分析系列十五 CPU使用率CPUP
操作系统·harmonyos
feiniao86518 小时前
2025年华为手机解锁BL的方法
华为·智能手机
塞尔维亚大汉9 小时前
OpenHarmony(鸿蒙南向)——平台驱动开发【I3C】
harmonyos·领域驱动设计
VVVVWeiYee9 小时前
BGP配置华为——路径优选验证
运维·网络·华为·信息与通信
今阳10 小时前
鸿蒙开发笔记-6-装饰器之@Require装饰器,@Reusable装饰器
android·app·harmonyos
余多多_zZ11 小时前
鸿蒙初学者学习手册(HarmonyOSNext_API14)_组件截图(@ohos.arkui.componentSnapshot (组件截图) )
学习·华为·harmonyos·鸿蒙·鸿蒙系统