(本篇笔记对应课程第 22 节)
P22《21.ArkUI-实现摇杆功能》
本节我们将小鱼动画案例中的按钮控制改为摇杆控制,用来熟悉和巩固动画效果相关的知识点。
分析实现思路:摇杆控制器包括大圆区域和里面的小圆球,通过用手指控制小圆球移动,来控制小鱼位置变化。
1、我们可以获取手指位置坐标{x,y},并计算出这个坐标值与大圆中心点坐标值的差值,即图中红色横线与竖线,用vx与vy表示。
2、有了这两个值,就可以得到手指与中心点连线与x轴正轴方向的夹角angle,如图中标识出的夹角。我们可以得到手指与中心点连线之间的距离(已知vx,vy,相当于已知直角三角形两条直角边,求斜边长度),这个距离如果超过大圆区域,就取大圆半径,因为小圆球的移动区域需要在大圆范围内,不能移动出大圆。
3、有了这个距离(图中蓝色直角三角形斜边长)和夹角angle,可以得到蓝色直角三角形的两条直角边长,也就是小圆球的移动坐标值。有了这个移动坐标值,我们就可以控制小圆球的移动了。
4、控制小鱼位置:小鱼移动需要有一个速度值,这个速度值相当于其在某个方向上移动出去的距离,也就是相当于黄色直角三角形的斜边长,有了斜边长和angle角度,可以得到黄色直角三角形的两条直角边长,即小鱼的移动坐标距离。
摇杆区静态代码:
给大摇杆添加touch事件:
事件中的逻辑思路如下几步注释:
screenX 与 screenY 指的是手指触摸点在整个屏幕中的坐标,而 x 与 y 指的是手指触摸点在当前容器中的坐标。我们需要的是 x 与 y
测试效果小圆球拖动效果,发现几个小问题:
首先,拖动小圆球拖不动,只有在大圆区域内拖动才能使小圆球移动,这是因为touch事件仅仅是加在大圆上的。
解决:将加在大圆上的 touch 事件改为加在 大圆与小圆球的外层容器上:
其次,小圆球的拖动不够圆滑,没有动画效果
解决:用显式动画的方式给小球增加动画效果:
使用curves库(需要导入)中的动画效果:
再次,手指松开后小球位置需要还原:这就需要对事件类型做出判断,根据不同事件分别进行处理:手指松开时还原小球位置;手指移动时处理小球跟随效果,即我们写出的1-5步的代码:
给手指松开小球位置还原增加动画效果:
到这里就完成了摇杆小球跟随手指移动的效果。
接下来我们来控制小鱼的位置:
此时测试发现效果是:只有移动小球时,小鱼位置才随之改变,移动小球停止,小鱼就不动了。效果不够圆滑生动。实现小鱼在手指控制小球过程中持续移动,这就需要开启一个定时器。
要想人眼看到动画效果,最低每秒要24帧。定时器设置为40毫秒时,每秒就是25帧。
此时发现小鱼不会随着游动方向改变角度,完善小鱼的角度:
实践:
typescript
// AnimationPage.ets
import router from '@ohos.router'
import curves from '@ohos.curves'
@Styles function btnStyle(){
.backgroundColor('rgba(0,0,0,0.2)')
}
@Entry
@Component
struct AnimationPage {
// 小鱼坐标
@State fishX: number = 400
@State fishY: number = 180
// 小鱼角度
@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() {
Column() {
Stack(){
// 返回按钮
Button('返回')
.position({x:0,y:0})
.btnStyle()
.onClick(()=>{
router.back()
})
// 开始游戏按钮
if(!this.isBegin){
Button('开始游戏')
.onClick(()=>{
animateTo(
{duration:1000},
()=>{
this.isBegin = true
}
)
})
}else{
// 小鱼图片
Image(this.src)
.width('80')
.position({x: this.fishX - 40, y: this.fishY - 40})
.rotate({angle:this.angle, centerX:'50%', centerY:'50%'})
// .animation({duration:500, tempo:1})
.transition({
type:TransitionType.Insert,
opacity:0,
translate:{x: -200}
})
}
// 操作按钮
/*Row(){
Button('←')
.btnStyle()
.type(ButtonType.Circle)
.onClick(()=>{
animateTo(
{ duration:500 },
()=>{
this.src = $r('app.media.fish_left')
this.fishX -= 40
}
)
})
Column({
space:20
}){
Button('↑')
.btnStyle()
.type(ButtonType.Circle)
.onClick(()=>{
animateTo(
{duration:500},
()=>{
this.fishY -= 40
}
)
})
Button('↓')
.btnStyle()
.type(ButtonType.Circle)
.onClick(()=>{
animateTo(
{duration:500},
()=>{
this.fishY += 40
}
)
})
}
Button('→')
.btnStyle()
.type(ButtonType.Circle)
.onClick(()=>{
animateTo(
{ duration:500 },
()=>{
this.src = $r('app.media.fish')
this.fishX += 40
}
)
})
}
.width(120)
.position({x:10,y:250})
.justifyContent(FlexAlign.Center)*/
// 摇杆控制
Row(){
Circle({ width: this.maxRadius * 2, height:this.maxRadius * 2})
.fill('#20101010')
.position({x:this.centerX - this.maxRadius, y:this.centerX - this.maxRadius })
Circle({ width: this.radius * 2, height:this.radius * 2})
// .fill('#403A3A3A')
.fill('#d3601010')
.position({x:this.positionX - this.radius, y:this.positionY - this.radius })
}
.width(240)
.height(240)
.position({x:0, y:120})
.onTouch(this.handleTouchEvent.bind(this))
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.fish_bg'),ImageRepeat.NoRepeat)
.backgroundImageSize(ImageSize.Cover)
.backgroundImagePosition(Alignment.Bottom)
}
handleTouchEvent = (event:TouchEvent)=>{
switch (event.type){
case TouchType.Up:
// 还原小球位置
animateTo(
{ curve:curves.springMotion() },
()=>{
this.positionX = this.centerX
this.positionY = this.centerY
}
)
// 还原小鱼速度
this.speed = 0
// 还原小鱼角度
this.angle = 0
// 取消定时任务
clearInterval(this.taskId)
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)
// 5、计算摇杆小球的坐标
this.sin = Math.sin(angle)
this.cos = Math.cos(angle)
animateTo(
{ curve:curves.responsiveSpringMotion() },
()=>{
// 修改摇杆小球的坐标
this.positionX = this.centerX + distance * this.cos
this.positionY = this.centerY + distance * this.sin
// 修改小鱼的坐标
this.speed = 5
/*this.fishX += this.speed * this.cos
this.fishY += this.speed * this.sin*/
// 小鱼向右游
if(Math.abs(angle * 2) < Math.PI){
this.src = $r('app.media.fish')
}else{ // 小鱼向左游
this.src = $r('app.media.fish_left')
angle = angle < 0 ? angle + Math.PI : angle - Math.PI // todo 这句不太懂
}
// 修改小鱼的角度
this.angle = angle * 180 / Math.PI
}
)
break;
}
}
getDistance = (x:number,y:number)=>{
let d = Math.sqrt(x*x + y*y)
return Math.min(d, this.maxRadius)
}
}