HarmonyOS NEXT:实现电影列表功能展示界面

时至今日HarmonyOS NEXT早已发布运行了,等其正式推出并大规模商用后,HarmonyOS的历史使命就完成并将退出历史舞台,为用户提供丰富的应用选择。但是Harmony NEXT是在HarmonyOS基础上剔除安卓(AOSP)后的产品,属于全新的手机系统。

今天实现一个简单的小案例,从零开始讲解如何通过鸿蒙开发实现一个电影列表功能的案例。

目录

新建初始项目

菜单与电影页

数据接口封装

加载与适配页

电影列表界面


新建初始项目

进入到编辑器中点击左上角的文件点击新建项目,然后选择空的 Empty Ability 进行创建:

然后接下来输入自己的项目名称就行,点击finish即可:

运行本地预览器,可以看到我们的初始项目已经跑通:

菜单与电影页

接下来开始编写我们类似H5端的tabBar导航菜单按钮的功能,这里我们可以借助官网给我们提供的选项卡(Tabs)进行类似的操作,如下所示:

接下来我们开始使用这个tabs选项卡的功能,首选我们先在pages目录下新建两个ArkTS文件然后把这两个文件都暴露出去,如下所示:

然后我们在index.ets文件下通过模块导入的方式,将两个文件进行一个导入,代码如下:

TypeScript 复制代码
import Home from './home'
import Cinema from './cinema'

@Entry
@Component
struct Index {
  build() {
    Tabs({ barPosition: BarPosition.End }) {
      TabContent() {
        Home().height('100%')
      }.tabBar("电影")
      TabContent() {
        Cinema().height('100%')
      }.tabBar("影院")
    }
  }
}

效果如下所示:

接下来我们就可以在首页组件中撰写相应的首页内容,这里我们也是借助了官方文档中的轮播器进行实现轮播图的操作,具体的代码如下所示:

TypeScript 复制代码
@Component
struct Home {
  @State flag: boolean = true
  imgList: string[] = [
    "http://124.223.69.156:5500/h5-01.jpg",
    "http://124.223.69.156:5500/h5-02.jpg",
    "http://124.223.69.156:5500/h5-03.png",
  ]
  build() {
    Scroll() {
      Column() {
        // 轮播图
        Swiper() {
          ForEach(this.imgList, (item: string) => {
            Image(item).width('100%').height(180)
          })
        }
        .autoPlay(true) // 自动轮播
        .loop(true) // 无缝衔接
        // 即将上映
        Flex({ justifyContent: FlexAlign.SpaceAround }) {
          Text('正在热映')
            .padding(10)
            .border({ width: { bottom: this.flag ? 3 : 0 } })
            .borderColor(Color.Red)
            .onClick(() => this.flag = true)
          Text('即将上映')
            .padding(10)
            .border({ width: { bottom: !this.flag ? 3 : 0 } })
            .borderColor(Color.Red)
            .onClick(() => this.flag = false)
        }.margin({ top: 10, bottom: 10 })
        if (this.flag) {
          Text('正在热映')
        } else {
          Text('即将上映')
        }
      }
    }

  }
}
export default Home

最终呈现的效果如下所示:

上面我们仅仅是实现了静态页面的内容,如果想实现动态渲染的话就需要调用相应的接口函数,这里我们可以借助官方给我们提供的第三方仓库,网站:地址 如下所示:

然后我们下载我们调用接口的第三方库,终端执行如下命令进行安装:

TypeScript 复制代码
ohpm install @ohos/axios

然后我们可以在项目的该文件下可以查看项目中安装的相关依赖信息:

数据接口封装

接下来开始对我们调用第三方接口进行一个初始的axios数据封装,在ets目录下新建utils工具目录,然后在工具目录当中封装如下的数据:

TypeScript 复制代码
import axios, { AxiosResponse } from "@ohos/axios"

// 参数统一处理
interface options_type {
  url: string,
  method?: string,
  data?: object,
  params?: object,
  headers?: object
}
// 请求返回类型
interface request_dataType {
  status: number,
  msg?: string,
  data: object
}

// 创建axios实例
const request = axios.create({
  baseURL: "https://m.maizuo.com/",
  headers: {
    'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"1596502176387264316178433","bc":"310100"}'
  }
})
const http = (options: options_type) => {
  const method = options.method || 'GET'
  if (method.toLowerCase() === 'get') {
    options.params = options.data
  }
  return request(options)
}
// 针对请求返回的结果处理
const resultFn = async<T> (options: options_type): Promise<T> => {
  const result: AxiosResponse<request_dataType> = await http(options)
  return result.data as T
}

export default resultFn

然后接下来我们继续在ets目录下新建api目录,用于存放后期的api文件,这里我们新建home.ets文件,然后在该文件中编写对应的home页的内容数据:

TypeScript 复制代码
import request from '../utils/request'

interface return_Home_type {
  files: filems[],
  total: number
}
interface filems {
  poster?: string,
  name?: string,
  filmId: number,
  nation: string,
  runtime: number,
  actors: [
    name: string
  ]
}
interface myHeader {
  "X-Host": string
}

// 获取首页数据
export const getHomeData = (page: number) => {
  return request<return_Home_type>({
    url: `gateway?cityId=310100&pageNum=${page}&pageSize=15&type=1&k=266615`,
    headers: {
      "X-Host": "mall.film-ticket.film.list"
    } as myHeader,
    method: "GET"
  })
}

然后我们在首页中通过调用生命周期函数 aboutToAppear ,在页面加载之前调用函数打印数据:

然后我们调用写好的接口函数获取对应的数据:

TypeScript 复制代码
aboutToAppear(): void {
  getHomeData(this.page).then(res => {
    console.log(JSON.stringify(res))
    this.moveData.films = res.films?.map((item: filems) => {
      item.poster = item.poster?.replace("pic.maizuo.com", "static.maizuo.com/pc/v5")
      return item
    })
  })
}

通过props的方式,将获取到的接口数据传递给子组件:

TypeScript 复制代码
import { return_Home_type, filems, actors_type } from '../api/home'

@Component
struct MoveList {
  @Prop moveData: return_Home_type
  @Link page: number
  // 处理参演人员数据
  fillter_actors(arr: actors_type[] | undefined) {
    if (!arr) return "暂无主演"
    return arr.map((item: actors_type) => {
      return item.name
    }).join(' ') // 通过数组转为字符串,用空格拼接
  }
  build() {
    Column() { // 兼容不同的终端
      GridRow({ gutter: { y: 20 } }) {
        ForEach(this.moveData.films, (item: filems) => {
          GridCol({
            span: { sm:12, md:6, lg: 3 }
          }) {
            Flex({ justifyContent: FlexAlign.SpaceBetween }) {
              Image(item.poster).width('20%').height(100).margin({ right: 10 })
              Column() {
                Text(item.name).fontSize(20).fontWeight(FontWeight.Bold).lineHeight(30)
                Text(this.fillter_actors(item.actors))
                Row() {
                  Text(item.nation + '|').fontSize(15)
                  Text(`${item.runtime}`).fontSize(15)
                }
              }.alignItems(HorizontalAlign.Start).width('80%')
            }.padding(10)
          }
        })
      }
    }
  }
}

export default MoveList

最终呈现的效果如下所示:

加载与适配页

为了实现性能上的优化,这里我们需要实现一下下拉加载更多的功能,在Scroll容器当中是有下拉触底的函数的,这里我们简单的调用一下:

为了方便调用,这里我们将之前获取数据的函数抽离一下:

TypeScript 复制代码
// 请求电影列表数据
getMovieData() {
  getHomeData(this.page).then(res => {
    this.moveData.films = res.films?.map((item: filems) => {
      item.poster = item.poster?.replace("pic.maizuo.com", "static.maizuo.com/pc/v5")
      return item
    })
    // 如果是第一页则直接覆盖move_data
    if (this.page === 1) {
      this.moveData = res
    } else {
      // 如果是其他页则直接对films进行拼接
      (this.moveData as return_Home_type).films =
        (this.moveData as return_Home_type).films?.concat(res.films as filems[])
    }
  })
}
aboutToAppear(): void {
  this.getMovieData()
}

最终呈现的效果如下所示:

接下来我们给其加上一个回到顶部的按钮,这里我们首先需要给滚动容器创建实例:

然后设置停止滚动触发的函数,然后根据条件的判断来动态显示按钮:

在列表界面,调用屏幕适配变化的函数,来动态的调用获取数据的函数:

呈现的效果如下所示:

电影列表界面

接下来开始实现影院列表界面,首先我们先编写相应的接口函数,代码如下所示:

TypeScript 复制代码
import request from '../utils/request'

interface MyHeader {
  "X-Host": string
}
interface return_cinema_type {
  cinemas: cinemas[]
}
export interface cinemas {
  name: string,
  address: string
}
interface return_city_type {
  cities: cities[]
}
export  interface cities {
  name: string,
  cityId: number
}
// 获取影院列表
export const getCinemaData = (cityId: number) => {
  return request<return_cinema_type>({
    url: `gateway?cityId=${cityId}&ticketFlag=1&k=2500238`,
    headers: {
      "X-Host": "mall.film-ticket.cinema.list"
    } as MyHeader,
    method: "get"
  })
}
// 获取城市列表
export const getCity = () => {
  return request<return_city_type>({
    url: `gateway?k=2323064`,
    headers: {
      "X-Host": "mall.film-ticket.cinema.list"
    } as MyHeader,
    method: "get"
  })
}

接下来我们开始编写对应的影院列表内容数据的布局:

TypeScript 复制代码
@Component
struct Cinema {
  @State cityName: string = '上海' // 城市名称
  @State cityId: number = 310100
  @State cinemas: cinemas[] = [] // 影院列表
  cityDialog: CustomDialogController = new CustomDialogController({
    builder: CityDialog({
      updateCity: (city: cities) => { this.updateCity(city) }
    })
  })
  updateCity(city: cities) {
    // 关闭弹框
    this.cityDialog.close()
    // 调用列表接口
    this.getData(city.cityId)
    // 修改显示名称
    this.cityName = city.name
  }

  // 请求数据
  getData(cityId: number) {
    getCinemaData(cityId).then(res=>{
      this.cinemas = res.cinemas
    })
  }
  aboutToAppear(): void {
    this.getData(this.cityId)
  }
  build() {
    Scroll() {
      Column() {
        Text(this.cityName)
          .width('100%')
          .margin({ bottom: 10 })
          .textAlign(TextAlign.Center)
          .onClick(()=>{
            // 展示弹框
            this.cityDialog.open()
          })
        ForEach(this.cinemas, (item: cinemas) => {
          Text(item.name).fontSize(18).margin({ left: 5 })
          Text(item.address).fontSize(15).fontColor(Color.Gray).margin({ left: 5 })
        })
      }.alignItems(HorizontalAlign.Start)
    }

  }
}
export default Cinema

这里我们用到了一个弹框的效果,代码如下所示:

TypeScript 复制代码
// 创建城市弹框
@CustomDialog
@Component
struct CityDialog {
  @State cities: cities[] = []
  controller: CustomDialogController
  // 定义父组件传入的通信函数
  updateCity: (city: cities) => void = () => {}
  aboutToAppear(): void {
    getCity().then(res => {
      this.cities = res.cities
    })
  }
  build() {
    Scroll() {
      Text('请选择城市')
        .fontSize(20)
        .fontColor(Color.Red)
        .margin(5)
        .padding(5)
        .border({ width: { bottom: 1 } })
      ForEach(this.cities, (item: cities) => {
        Text(item.name)
          .width('100%')
          .height(50)
          .textAlign(TextAlign.Center)
          .onClick(() => {
            this.updateCity(item)
          })
      })
    }
  }
}

最终呈现的效果如下所示:

具体的界面搭建与接口调用参考的是 卖座电影 的一个H5案例,如下所示:

截止现在,一个简易的电影列表展示内容已经做完了,大家可以自行去练习一下!

相关推荐
SuperHeroWu72 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
期待未来的男孩2 小时前
华为FusionCube 500-8.2.0SPC100 实施部署文档
华为
岳不谢4 小时前
VPN技术-VPN简介学习笔记
网络·笔记·学习·华为
zhangjr05755 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
努力的小雨7 小时前
零基础入门gRPC:从 0 实现一个Hello World
经验分享
有过~8 小时前
XviD4PSP视频无损转换器
经验分享·电脑
诗歌难吟46411 小时前
初识ArkUI
harmonyos
SameX11 小时前
HarmonyOS Next 设备安全特性深度剖析学习
harmonyos
催催1211 小时前
手机领夹麦克风哪个牌子好,哪种领夹麦性价比高,热门麦克风推荐
网络·人工智能·经验分享·其他·智能手机
郭梧悠13 小时前
HarmonyOS(57) UI性能优化
ui·性能优化·harmonyos