一、动画
1、属性动画
animation,可以通过配置动画时间duration等参数,实现移动时的平滑过度
写了个小鱼游动的小案例
@Entry
@Component
struct ActionPage {
@State fish: Resource = $r('app.media.fish_right') //小鱼图片
@State fishX: number = 200 //初始化小鱼横坐标
@State fishY: number = 100 //初始化小鱼纵坐标
build() {
Stack(){ //层叠布局组件
Image($r('app.media.fish_back')).width('100%').height('100%') //背景
RelativeContainer() {
Image(this.fish) //小鱼
.position({
x: this.fishX - 30,
y: this.fishY - 40
})
.rotate({
angle: 0,
centerX: "50%",
centerY: "50%"
})
.width(40)
.height(40)
.animation({
duration: 500, //动画时长,单位毫秒
curve: Curve.EaseInOut
})
Row() { //操作区
Button('←').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{ duration: 500 },
() => {
this.fishX -= 20
this.fish = $r('app.media.fish_left')
}
)
})
Column({ space: 40 }) {
Button('↑').backgroundColor('#20101010')
.onClick(() => {
this.fishY -= 20
})
Button('↓').backgroundColor('#20101010')
.onClick(() => {
this.fishY += 20
})
}
Button('→').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{ duration: 500 },
() => {
this.fishX += 20
this.fish = $r('app.media.fish_right')
}
)
})
}
.height(240)
.width(240)
.justifyContent(FlexAlign.Center)
.position({x:0,y:120})
}
.height('100%')
.width('100%')
}
}
}
2、组件转场动画
组件插入或者移除时的动画,通过transition属性来配置
可修改上面的小鱼案例,添加小鱼入场动画
通过isBegin变量控制小鱼组件的加载
官网动画相关文档,可以多看看
应用开发导读-基础入门 | 华为开发者联盟 (huawei.com)
3、摇杆扩展小案例
改造上述小鱼游戏案例,将移动操作区域改成摇杆控制
思路:
1、定义摇杆区域,大圆套小圆
2、根据手指移动计算出小球移动,并且不超出大圆
3、根据小球移动角度,和小鱼移动速度,计算出小鱼的角度和最终坐标
ps:本案例是根据B站 黑马程序员_鸿蒙课程案例写的,可以帮助理解
import { curves } from '@kit.ArkUI'
@Entry
@Component
struct ActionPage {
@State fish: Resource = $r('app.media.fish_right')
//小鱼坐标
@State fishX: number = 200
@State fishY: number = 100
//小鱼角度
@State angle: number = 0
//开始游戏
@State isBegin: boolean = false
// 摇杆中心区域坐标
private centerX: number = 120
private centerY: number = 120
//大、小圈半径
private maxRadius: number = 100
private radius: number = 20
//摇杆小球的初始位置
@State positionX: number = this.centerX;
@State positionY: number = this.centerY;
// 角度正弦和余弦
sin: number = 0
cos: number = 0
//小鱼的移动速度
speed: number = 0
//任务id
taskId: number = -1
build() {
Stack() {
Image($r('app.media.fish_back')).width('100%').height('100%')
RelativeContainer() {
if (!this.isBegin) {
Button('开始游戏')
.onClick(() => {
animateTo(
{ duration: 1000 },
() => {
this.isBegin = true
}
)
})
.position({ x: 335, y: 130 })
} else {
Image(this.fish)
.position({
x: this.fishX - 30,
y: this.fishY - 40
})
.rotate({
angle: this.angle,
centerX: "50%",
centerY: "50%"
})
.width(40)
.height(40)
.animation({
duration: 500, //动画时长,单位毫秒
curve: Curve.EaseInOut
})
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: { x: -220 }
})
}
//摇杆
Row() {
Circle({ width: this.maxRadius * 2, height: this.maxRadius * 2 })
.fill('#20101010')
.position({ x: this.centerX - this.maxRadius, y: this.centerY - this.maxRadius })
Circle({ width: this.radius * 2, height: this.radius * 2 })
.fill('#20101010')
.position({ x: this.positionX - this.radius, y: this.positionY - this.radius })
}
.onTouch(this.handleTouchEvent.bind(this))
.height(240)
.width(240)
.justifyContent(FlexAlign.Center)
.position({ x: 0, y: 120 })
}
.height('100%')
.width('100%')
}
}
/**处理手指移动*/
handleTouchEvent(event: TouchEvent) {
switch (event.type) {
case TouchType.Up:
//清除定时任务
clearInterval(this.taskId)
this.speed = 0
animateTo(
{ curve: curves.responsiveSpringMotion() },
() => {
this.positionX = this.centerX
this.positionY = this.centerY
}
)
this.angle = 0
break;
case TouchType.Down:
//开始定时任务,为了保证按下时小鱼坐标的持续变化
this.taskId = setInterval(() => {
this.fishX += this.speed * this.cos
this.fishY += this.speed * this.sin
}, 40)
break;
case TouchType.Move:
//1、获取手指坐标
let x = event.touches[0].x
let y = event.touches[0].y
//2、手指与中心点的坐标差值
let vx = x - this.centerX
let vy = y - this.centerY
//3、手指和中心的连线与X轴正半轴的夹角
let angles = Math.atan2(vy, vx)
//4、手指与中心的的距离
let distance = this.getDistance(vx, vy)
//5、摇杆小球的坐标
this.cos = Math.cos(angles)
this.sin = Math.sin(angles)
animateTo(
{ curve: curves.responsiveSpringMotion() },
() => {
this.positionX = this.centerX + distance * this.cos
this.positionY = this.centerY + distance * this.sin
//小鱼转向角度
if (Math.abs(angles * 2) < Math.PI) {
this.fish = $r('app.media.fish_right')
} else {
this.fish = $r('app.media.fish_left')
angles = angles < 0 ? angles + Math.PI : angles - Math.PI
}
//修改小鱼的坐标
this.angle = angles * 100 / Math.PI
this.speed = 5
}
)
break;
}
}
//已知直角边,计算斜边长
getDistance(x: number, y: number) {
let d = Math.sqrt(x * x + y * y)
return Math.min(d, this.maxRadius)
}
}
二、stage模型
1、基本概念
stage是一种应用模型
应用模型:系统为开发者提供的应用程序必备的组件和运行机制。相当于一个模板,基于统一的模型进行应用开发,使应用开发更简单、高效
目前有两种应用模型:FA 和 Stage
此处,我们先了解stage,它由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此得名
2、应用配置文件
有两种:全局配置app.json5、模块的配置modle.json5
应用配置文件_鸿蒙官网有对配置中各个属性的详细描述,查询很方便
3、UIAbility生命周期
Foreground和Background状态分别在UIAbility实例切换至前台和切换至后台时触发
在代码里可以找到对应描述
可以试着在日志中查看生命周期的打印顺序
4、页面及组件的生命周期
注意页面生命周期函数得在像@entry这样修饰的页面使用
另外两种aboutToAppear、aboutToDisappear则可以在自定义组件中使用
aboutToAppear(): void {
console.log("组件加载时触发")
}
onPageShow(): void {
console.log("页面每次显示时触发一次,
包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效")
}
onBackPress(): boolean | void {
console.log("用户点击返回按钮时触发")
}
onPageHide(): void {
console.log("页面隐藏,应用进入后台")
}
aboutToDisappear(): void {
console.log("组件销毁时触发")
}
三、网络连接
1、http请求数据
可参考HTTP数据请求-鸿蒙官方文档
官网文档描述的非常细致,包含了使用案例和对应的具体参数含义
还是常规步骤,先引入再使用即可
// 引入包名
import { http } from '@kit.NetworkKit';
// 引入返回报错类
import { BusinessError } from '@kit.BasicServicesKit';
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
httpRequest.on('headersReceive', (header: Object) => {
console.info('header: ' + JSON.stringify(header));
});
class Header {
public contentType: string;
constructor(contentType: string) {
this.contentType = contentType;
}
}
httpRequest.request(
// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
"EXAMPLE_URL",
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定,可以是字符串或者对象
extraData: 'data to send',
expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: true, // 可选,默认为true
priority: 1, // 可选,默认为1
// 开发者根据自身业务需要添加header字段
header: new Header('application/json'),
readTimeout: 60000, // 可选,默认为60000ms
connectTimeout: 60000, // 可选,默认为60000ms
},
(err: BusinessError, data: http.HttpResponse) => {
if (!err) {
// data.result为HTTP响应内容,可根据业务需要进行解析
console.info('Result:' + JSON.stringify(data.result));
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
httpRequest.destroy();
} else {
console.info('error:' + JSON.stringify(err));
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
httpRequest.destroy();
}
});
2、第三方库axios
因为axios是第三方库,所以需要三方库的包管理工具ohpm来管理
2.1 检查ohpm
终端中输入命令
ohmp -v
可以看的ohmp的版本号
(注意:比较旧的版本可能需要自己下载安装ohmp,但版本为“HarmonyOS NEXT Developer Beta1”,版本文档更新于2024-06-25的肯定已经自动配置好了的)
2.2 下载安装axios
也是可以参考OpenHarmony三方库中心仓
里面包含了常用三方库的配置和使用说明
1、module.json5中要配置允许访问网络
2、输入安装命令:ohpm install @ohos/axios
3、安装完成,使用axios
import axios from '@ohos/axios'
interface userInfo{
id: number
name: string,
phone: number
}
// 向给定ID的用户发起请求
axios.get<userInfo, AxiosResponse<userInfo>, null>('/user?ID=12345')
.then((response: AxiosResponse<userInfo>)=> {
// 处理成功情况
console.info("id" + response.data.id)
console.info(JSON.stringify(response));
})
.catch((error: AxiosError)=> {
// 处理错误情况
console.info(JSON.stringify(error));
})
.then(()=> {
// 总是会执行
});
四、数据持久化
概念:将内存中的数据通过文件或数据库的形式保存到设备上。目的是为了常用的数据能方便的存取
1、用户首选项
全局唯一存储的地方,轻量级的,所以不能存放太多数据,也不支持通过配置加密,一般存放“字体大小设置、是否开启夜间模式”等
通过用户首选项实现数据持久化-官网文档
2、关系型数据库
存储包含复杂关系数据,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等
通过关系型数据库实现数据持久化-官网文档
六、通知
可借助Notification Kit将应用产生的通知直接在客户端本地推送给用户,本地通知根据通知类型及发布场景会产生对应的铃声、震动、横幅、锁屏、息屏、通知栏提醒和显示。
Notification Kit简介-Notification Kit(用户通知服务)-应用服务 | 华为开发者联盟 (huawei.com)
- 基础通知: 发送短信息、提示信息等
- 进度条通知:应用于文件下载、事务处理进度显示
- 通知行为意图:当发布通知时,如果期望用户可以通过点击通知栏拉起目标应用组件或发布公共事件
详细使用可参考发布通知-官网文档