时至今日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案例,如下所示:
截止现在,一个简易的电影列表展示内容已经做完了,大家可以自行去练习一下!