基于ArkUI的水波紋動(dòng)畫(huà)開(kāi)發(fā)
效果呈現(xiàn)
本例最終效果圖如下:
環(huán)境要求
本例基于以下環(huán)境開(kāi)發(fā),開(kāi)發(fā)者也可以基于其他適配的版本進(jìn)行開(kāi)發(fā):
- IDE: DevEco Studio 3.1 Beta2
- SDK: Ohos_sdk_public 3.2.11.9(API Version 9 Release)
實(shí)現(xiàn)思路
本實(shí)例涉及到的主要特性及其實(shí)現(xiàn)方案如下:
- UI框架:使用Grid,GridItem等容器組件組建UI框架。
- 按鈕渲染:通過(guò)自定義numBtn組件(含Column、Button、Stack、Text等關(guān)鍵組件以及visibility屬性),進(jìn)行數(shù)字按鈕的渲染。
- 按鈕狀態(tài)變化:設(shè)置狀態(tài)變量unPressed,控制按鈕的當(dāng)前狀態(tài),向Column組件添加onTouch事件,監(jiān)聽(tīng)按鈕的當(dāng)前狀態(tài)。
- 默認(rèn)狀態(tài)為按鈕放開(kāi)狀態(tài)(unPressed為true)。
- 當(dāng)按鈕按下時(shí),更新按鈕的狀態(tài)(unPressed:true -> false)。
- 當(dāng)按鈕放開(kāi)時(shí),更新按鈕的狀態(tài)(unPressed:false -> true)。
- 按鈕動(dòng)畫(huà)展示:使用屬性動(dòng)畫(huà)以及組件內(nèi)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)繪制按鈕不同狀態(tài)下的動(dòng)畫(huà)。
- 當(dāng)按鈕按下時(shí),使用顯式動(dòng)畫(huà)(animateTo)加載動(dòng)畫(huà):插入按下時(shí)的Row組件,同時(shí)加載水波的聚攏效果。
- 當(dāng)按鈕放開(kāi)時(shí),使用組件內(nèi)轉(zhuǎn)場(chǎng)加載動(dòng)畫(huà):插入放開(kāi)時(shí)的Row組件,同時(shí)加載水波的擴(kuò)散效果。
開(kāi)發(fā)步驟
針對(duì)實(shí)現(xiàn)思路中所提到的內(nèi)容,具體關(guān)鍵開(kāi)發(fā)步驟如下:
先通過(guò)Grid,GridItem等容器組件將UI框架搭建起來(lái),在GuidItem中引用步驟2中的自定義數(shù)字按鈕numBtn構(gòu)建出數(shù)字柵格。
具體代碼如下:
private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
...
Column() {
Grid() {
ForEach(this.numGrid, (item: number, index: number) => {
GridItem() {
...
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width(330)
.height(440)
}.width('100%').height('100%')
通過(guò)Column、Button、Stack、Text等關(guān)鍵組件以及visibility屬性構(gòu)建自定義數(shù)字按鈕numBtn。
具體代碼如下:
@Component
struct numBtn {
···
build() {
Column() {
Button() {
stack(){
...
Text(`${this.item}`).fontSize(30)
}
...
}
.backgroundColor('#ccc')
.type(ButtonType.Circle)
.borderRadius(100)
.width(100)
.height(100)
}
.visibility(this.item == -1 ? Visibility.Hidden : Visibility.Visible)
.borderRadius(100)
}
}
設(shè)置狀態(tài)變量unPressed,監(jiān)聽(tīng)當(dāng)前數(shù)字按鈕的狀態(tài),同時(shí)向Column組件添加onTouch事件,獲取并更新按鈕的當(dāng)前狀態(tài),從而可以根據(jù)監(jiān)聽(tīng)到的按鈕狀態(tài)加載對(duì)應(yīng)的動(dòng)畫(huà)效果。
具體代碼塊如下:
//狀態(tài)變量unPressed,用于監(jiān)聽(tīng)按鈕按下和放開(kāi)的狀態(tài)
@State unPressed: boolean = true
...
// 添加onTouch事件,監(jiān)聽(tīng)狀態(tài)
.onTouch((event: TouchEvent) => {
// 當(dāng)按鈕按下時(shí),更新按鈕的狀態(tài)(unPressed:true -> false)
if (event.type == TouchType.Down) {
animateTo({ duration: 400 }, () => {
this.unPressed = !this.unPressed
this.currIndex = this.index
})
}
// 當(dāng)按鈕放開(kāi)時(shí),更新按鈕的狀態(tài)(unPressed:false -> true)
if (event.type == TouchType.Up) {
animateTo({ duration: 400 }, () => {
this.unPressed = !this.unPressed
})
}
})
根據(jù)按鈕組件的按下/放開(kāi)狀態(tài),通過(guò)if-else語(yǔ)句選擇插入的Row組件,并隨之呈現(xiàn)不同的水波動(dòng)畫(huà)效果(按下時(shí)水波聚攏,放開(kāi)時(shí)水波擴(kuò)散)。
具體代碼塊如下:
Stack() {
Row() {
// 判斷當(dāng)前按鈕組件為放開(kāi)狀態(tài)
if (this.unPressed && this.currIndex == this.index) {
// 插入Row組件,配置過(guò)渡效果
Row()
.customStyle()
.backgroundColor('#fff')
// 水波紋擴(kuò)散動(dòng)畫(huà):從Row組件的中心點(diǎn)開(kāi)始放大,scale{0,0}變更scale{1,1}(完整顯示)
.transition({
type: TransitionType.Insert,
opacity: 0,
scale: { x: 0, y: 0, centerY: '50%', centerX: '50%' }
})
}
// 判斷當(dāng)前按鈕組件為按下?tīng)顟B(tài)
else if (!this.unPressed && this.currIndex == this.index) {
// 插入Row組件,配置過(guò)渡效果
Row()
.customStyle()
.backgroundColor(this.btnColor)
.scale(this.btnScale)
.onAppear(() => {
// 水波紋聚攏動(dòng)畫(huà):Row組件backgroundColor屬性變更(#ccc -> #fff),插入動(dòng)畫(huà)過(guò)渡效果,scale{1,1}(完整顯示)變化為scale{0,0}
animateTo({ duration: 300,
// 聚攏動(dòng)畫(huà)播放完成后,需要銜接擴(kuò)散動(dòng)畫(huà),Row組件backgroundColor屬性變更(#fff -> #ccc),插入動(dòng)畫(huà)過(guò)渡效果,scale{0,0}變化為scale{1,1}(完整顯示)
onFinish: () => {
this.btnColor = '#ccc'
this.btnScale = { x: 1, y: 1 }
} },
() => {
this.btnColor = '#fff'
this.btnScale = { x: 0, y: 0 }
})
})
}
// 其他狀態(tài)
else {
Row()
.customStyle()
.backgroundColor('#fff')
}
}
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.borderRadius(100)
Text(`${this.item}`).fontSize(30)
}
.customStyle()
完整代碼
示例代碼如下:
@Entry
@Component
export default struct dragFire {
private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
build() {
Column() {
Grid() {
ForEach(this.numGrid, (item: number, index: number) => {
GridItem() {
numBtn({ item: item, index: index })
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width(330)
.height(440)
}.width('100%').height('100%')
}
}
@Component
struct numBtn {
private currIndex: number = -1
//狀態(tài)變量unPressed,用于控制按鈕的狀態(tài)
@State unPressed: boolean = true
@State btnColor: string = '#ccc'
index: number
item: number
@State btnScale: {
x: number,
y: number
} = { x: 1, y: 1 }
@Styles customStyle(){
.width('100%')
.height('100%')
.borderRadius(100)
}
build() {
Column() {
Button() {
Stack() {
Row() {
// 判斷當(dāng)前組件為放開(kāi)狀態(tài)
if (this.unPressed && this.currIndex == this.index) {
// 插入Row組件,配置過(guò)渡效果
Row()
.customStyle()
.backgroundColor('#fff')
// 水波紋擴(kuò)散動(dòng)畫(huà):Row組件backgroundColor屬性變更(#fff -> #ccc),系統(tǒng)插入動(dòng)畫(huà)過(guò)渡效果,從組建的中心點(diǎn)開(kāi)始放大,scale{0,0}變更scale{1,1}
.transition({
type: TransitionType.Insert,
opacity: 0,
scale: { x: 0, y: 0, centerY: '50%', centerX: '50%' }
})
}
// 判斷當(dāng)前組件為按下?tīng)顟B(tài)
else if (!this.unPressed && this.currIndex == this.index) {
// 插入Row組件,配置過(guò)渡效果
Row()
.customStyle()
.backgroundColor(this.btnColor)
.scale(this.btnScale)
.onAppear(() => {
// 水波紋聚攏動(dòng)畫(huà):Row組件backgroundColor屬性變更(#ccc -> #fff),插入動(dòng)畫(huà)過(guò)渡效果,scale{1,1}變化為scale{0,0}
animateTo({ duration: 300,
// 聚攏動(dòng)畫(huà)播放完成后,需要銜接擴(kuò)散動(dòng)畫(huà),此時(shí)Row組件backgroundColor屬性變更(#fff -> #ccc),插入動(dòng)畫(huà)過(guò)渡效果,scale{0,0}變化為scale{1,1}
onFinish: () => {
this.btnColor = '#ccc'
this.btnScale = { x: 1, y: 1 }
} },
() => {
this.btnColor = '#fff'
this.btnScale = { x: 0, y: 0 }
})
})
}
// 其他狀態(tài)
else {
Row()
.customStyle()
.backgroundColor('#fff')
}
}
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.borderRadius(100)
Text(`${this.item}`).fontSize(30)
}
.customStyle()
}
.stateEffect(false)
.backgroundColor('#ccc')
.type(ButtonType.Circle)
.borderRadius(100)
.width(100)
.height(100)
}
.visibility(this.item == -1 ? Visibility.Hidden : Visibility.Visible)
.borderRadius(100)
// onTouch事件,監(jiān)聽(tīng)狀態(tài)
.onTouch((event: TouchEvent) => {
// 當(dāng)按鈕按下時(shí),更新按鈕的狀態(tài)(unPressed:true -> false)
if (event.type == TouchType.Down) {
animateTo({ duration: 400 }, () => {
this.unPressed = !this.unPressed
this.currIndex = this.index
})
}
// 當(dāng)按鈕放開(kāi)時(shí),更新按鈕的狀態(tài)(unPressed:false -> true)
if (event.type == TouchType.Up) {
animateTo({ duration: 400 }, () => {
this.unPressed = !this.unPressed
})
}
})
}
}