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案例,如下所示:

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

相关推荐
AORO_BEIDOU1 小时前
单北斗+鸿蒙系统+国产芯片,遨游防爆手机自主可控“三保险”
华为·智能手机·harmonyos
博览鸿蒙3 小时前
鸿蒙操作系统(HarmonyOS)的应用开发入门
华为·harmonyos
星河梦瑾4 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
长潇若雪6 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
志-AOX7 小时前
C语言入门指南:从零开始的编程之路
经验分享
WANGWUSAN668 小时前
Python高频写法总结!
java·linux·开发语言·数据库·经验分享·python·编程
Damon小智10 小时前
HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别
华为·harmonyos
赵谨言10 小时前
基于python+django的外卖点餐系统
经验分享·python·毕业设计
stm 学习ing11 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl
十二测试录11 小时前
Python基础——字符串
开发语言·经验分享·python·程序人生·职场发展