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

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

相关推荐
前端御书房3 小时前
HarmonyOS 组件
pytorch·深度学习·harmonyos
小粥学姐5 小时前
25维谛技术面试最常见问题面试经验分享总结(包含一二三面题目+答案)
经验分享·面试·职场和发展·求职招聘·远程工作·校园
jmoych5 小时前
华为LTC流程架构分享
华为·架构
2101_824775825 小时前
华为设备所有查看命令以及其对应作用
网络·网络协议·网络安全·华为
蒙娜丽宁5 小时前
华为仓颉语言入门(7):深入理解 do-while 循环及其应用
java·数据库·华为·仓颉
HeShen.5 小时前
机器学习Python实战-第三章-分类问题-1.K近邻算法
经验分享·python·机器学习·分类·近邻算法·sklearn
Android技术栈7 小时前
鸿蒙开发(NEXT/API 12)【穿戴设备信息查询】手机侧应用开发
嵌入式硬件·硬件架构·移动开发·harmonyos·鸿蒙·鸿蒙系统
helloxmg14 小时前
鸿蒙harmonyos next flutter混合开发之ohos工程引用 har 文件
flutter·华为·harmonyos
看山还是山,看水还是。18 小时前
华为常见命令手册
运维·服务器·网络·笔记·华为