动画
- 属性动画
- 显式动画
- 组件转场动画
手翻书的原理:
手翻书的原理主要是基于帧动画(Frame Animation)的概念。它通过在连续的画面中绘制微小的变化,制造出连贯的动画效果。每个画面被称为一帧,通过快速连续播放帧动画,让静止的图像变得活跃起来。手翻书可以被看作是动画片的简化版,利用人的视觉停留原理,通过手绘动画的形式展现内容。
我们只需要关注,一个组件的开始状态和结束状态,Arkui帮我们填充变化的过程
属性动画
小鱼案例
显式动画
// 小鱼动画案例按钮版
import router from '@ohos.router'
@Entry
@Component
struct AnimationPage {
// 小鱼坐标
@State fishX: number = 200
@State fishY: number = 180
// 小鱼角度
@State angle: number = 0
// 小鱼图片
@State src: Resource = $r('app.media.fish')
// 是否开始游戏
@State isBegin: boolean = false
build() {
Row() {
Stack(){
// 返回按钮
Button('返回')
.position({x:0, y: 0})
.backgroundColor('#20101010')
.onClick(() => {
// 返回上一页
router.back()
})
if(!this.isBegin){
// 开始按钮
Button('开始游戏')
.onClick(() => {
animateTo(
{duration: 1000},
() => {
// 点击后显示小鱼
this.isBegin = true
}
)
})
}else{
// 小鱼图片
Image(this.src)
.position({x: this.fishX - 20, y: this.fishY - 20})
.rotate({angle:this.angle, centerX: '50%', centerY: '50%'})
.width(40)
.height(40)
//.animation({duration: 500, curve: Curve.Smooth})
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: {x: -250}
})
}
// 操作按钮
Row(){
Button('←').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishX -= 20
this.src = $r('app.media.fish_rev')
}
)
})
Column({space: 40}){
Button('↑').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishY -= 20
}
)
})
Button('↓').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishY += 20
}
)
})
}
Button('→').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishX += 20
this.src = $r('app.media.fish')
}
)
})
}
.height(240)
.width(240)
.justifyContent(FlexAlign.Center)
.position({x:0,y:120})
}
.height('100%').width('100%')
}
.height('100%')
.width('100%')
.backgroundImage($r('app.media.sea'))
.backgroundImageSize({height: '105%', width: '100%'})
}
}
组件转场动画
注意入场出场动画要结合animateTo使用
显示与否的控制要放在animateTo里
// 小鱼动画案例按钮版
import router from '@ohos.router'
@Entry
@Component
struct AnimationPage {
// 小鱼坐标
@State fishX: number = 200
@State fishY: number = 180
// 小鱼角度
@State angle: number = 0
// 小鱼图片
@State src: Resource = $r('app.media.fish')
// 是否开始游戏
@State isBegin: boolean = false
build() {
Row() {
Stack(){
// 返回按钮
Button('返回')
.position({x:0, y: 0})
.backgroundColor('#20101010')
.onClick(() => {
// 返回上一页
router.back()
})
if(!this.isBegin){
// 开始按钮
Button('开始游戏')
.onClick(() => {
animateTo(
{duration: 1000},
() => {
// 点击后显示小鱼
this.isBegin = true
}
)
})
}else{
// 小鱼图片
Image(this.src)
.position({x: this.fishX - 20, y: this.fishY - 20})
.rotate({angle:this.angle, centerX: '50%', centerY: '50%'})
.width(40)
.height(40)
//.animation({duration: 500, curve: Curve.Smooth})
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: {x: -250} // 起始位置
})
}
// 操作按钮
Row(){
Button('←').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishX -= 20
this.src = $r('app.media.fish_rev')
}
)
})
Column({space: 40}){
Button('↑').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishY -= 20
}
)
})
Button('↓').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishY += 20
}
)
})
}
Button('→').backgroundColor('#20101010')
.onClick(() => {
animateTo(
{duration: 500},
() => {
this.fishX += 20
this.src = $r('app.media.fish')
}
)
})
}
.height(240)
.width(240)
.justifyContent(FlexAlign.Center)
.position({x:0,y:120})
}
.height('100%').width('100%')
}
.height('100%')
.width('100%')
.backgroundImage($r('app.media.sea'))
.backgroundImageSize({height: '105%', width: '100%'})
}
}
小鱼案例
计算方法,三角函数
Math.atan2()方法计算二维坐标系中任意一个点(x, y)和原点(0, 0)的连线与X轴正半轴的夹角大小。
// 所以Math.atan2(vy, vx)就可以计算出,角BAC的夹角,
// 3.计算手指与中心点连线和x轴正半轴的夹角,单位是弧度
let angle = Math.atan2(vy, vx)
// 5.计算摇杆小球的坐标
this.positionX = this.centerX + distance * this.cos
this.positionY = this.centerY + distance * this.sin
小鱼的角度
// 6.修改小鱼的角度
if(Math.abs(angle * 2) < Math.PI){ // 注意这里angle是弧度, 90度=π/2
this.src = $r('app.media.fish')
}else{
this.src = $r('app.media.fish_rev')
angle = angle < 0 ? angle + Math.PI : angle - Math.PI
}
this.angle = angle * 180 / Math.PI // 弧度转角度 弧度和角度的关系:弧度数/π=角度值/180°。
this.speed = 5
小鱼全部代码
import router from '@ohos.router'
import curves from '@ohos.curves'
@Entry
@Component
struct AnimationPage2 {
// 小鱼坐标
@State fishX: number = 200
@State fishY: number = 120
// 小鱼角度
@State angle: number = 0
// 小鱼图片
@State src: Resource = $r('app.media.fish')
// 是否开始游戏
@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() {
Row() {
Stack(){
// 返回按钮
Button('返回')
.position({x:0, y: 0})
.backgroundColor('#20101010')
.onClick(() => {
// 返回上一页
router.back()
})
// 开始按钮和小鱼
if(!this.isBegin){
// 开始按钮
Button('开始游戏')
.onClick(() => {
animateTo(
{duration: 1000},
() => {
// 点击后显示小鱼
this.isBegin = true
}
)
})
}else{
// 小鱼图片
Image(this.src)
.position({x: this.fishX - 20, y: this.fishY - 20})
.rotate({angle:this.angle, centerX: '50%', centerY: '50%'})
.width(40)
.height(40)
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: {x: -250}
})
}
// 摇杆
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('#403A3A3A')
.position({ x: this.positionX - this.radius, y: this.positionY - this.radius})
}
.height(240)
.width(240)
.justifyContent(FlexAlign.Center)
.position({x:0,y:120})
.onTouch(this.handleTouchEvent.bind(this)) // 手指在移动过程中,会移除大圆,所以我们把,监听放到大容器上
}
.height('100%').width('100%')
}
.height('100%')
.width('100%')
.backgroundImage($r('app.media.sea'))
.backgroundImageSize({height: '105%', width: '100%'})
}
// 处理手指移动的事件
handleTouchEvent(event: TouchEvent){
switch (event.type){ // 对手指移动类型做判断,离开停下小鱼,摇杆还原
case TouchType.Up:
// 还原小鱼速度
this.speed = 0
// 取消定时任务
clearInterval(this.taskId)
// 还原摇杆小球的坐标
animateTo(
{curve: curves.springMotion()}, // 还原动画
() => {
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 angle = Math.atan2(vy, vx)
// 4.计算手指与中心点的距离
let distance = this.getDistance(vx, vy)
this.sin = Math.sin(angle)
this.cos = Math.cos(angle)
animateTo(
{curve: curves.responsiveSpringMotion()}, // 跟守动画,持续的动画曲线
() => {
// 5.计算摇杆小球的坐标
this.positionX = this.centerX + distance * this.cos
this.positionY = this.centerY + distance * this.sin
// 6.修改小鱼的角度
if(Math.abs(angle * 2) < Math.PI){ // 注意这里angle是弧度, 90度=π/2
this.src = $r('app.media.fish')
}else{
this.src = $r('app.media.fish_rev')
angle = angle < 0 ? angle + Math.PI : angle - Math.PI
}
this.angle = angle * 180 / Math.PI // 弧度转角度 弧度和角度的关系:弧度数/π=角度值/180°。
this.speed = 5
}
)
break
}
}
getDistance(x: number, y: number){
let d = Math.sqrt(x * x + y * y)
return Math.min(d, this.maxRadius)
}
}