鴻蒙特效教程10-卡片展開/收起效果
在移動應用開發中,卡片是一種常見且實用的UI元素,能夠將信息以緊湊且易于理解的方式呈現給用戶。
本教程將詳細講解如何在HarmonyOS中實現卡片的展開/收起效果,通過這個實例,你將掌握ArkUI中狀態管理和動畫實現的核心技巧。
一、實現效果預覽
我們將實現一個包含多個卡片的頁面,整個交互過程都有平滑的動畫效果。
- 每個卡片默認只顯示標題,點擊右側箭頭按鈕后可以展開顯示詳細內容,再次點擊則收起。
- 實現"全部展開"和"全部收起"的功能按鈕。
二、實現步驟
步驟1:創建基礎頁面結構
首先,我們需要創建一個基本的頁面結構,包含一個標題和一個簡單的卡片:
@Entry
@Component
struct ToggleCard {build() {Column() {Text('卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 一個簡單的卡片Column() {Text('個人信息').fontSize(16).fontWeight(FontWeight.Medium)}.width('90%').padding(16).backgroundColor('#ECF2FF').borderRadius(12).margin({ top: 20 })}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
這段代碼創建了一個基本的頁面,頂部有一個標題,下方有一個簡單的卡片,卡片只包含一個標題文本。
步驟2:添加卡片標題行和展開按鈕
接下來,我們為卡片添加一個標題行,并在右側添加一個展開/收起按鈕:
@Entry
@Component
struct ToggleCard {build() {Column() {Text('卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 一個帶展開按鈕的卡片Column() {Row() {Text('個人信息').fontSize(16).fontWeight(FontWeight.Medium)Blank() // 占位,使按鈕靠右顯示Button() {Image($r('sys.media.ohos_ic_public_arrow_down')).width(24).height(24).fillColor('#3F72AF')}.width(36).height(36).backgroundColor(Color.Transparent)}.width('100%').justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)}.width('90%').padding(16).backgroundColor('#ECF2FF').borderRadius(12).margin({ top: 20 })}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
現在我們的卡片有了標題和一個展開按鈕,但點擊按鈕還沒有任何效果。接下來我們將添加狀態管理和交互邏輯。
步驟3:添加狀態變量控制卡片展開/收起
要實現卡片的展開/收起效果,我們需要添加一個狀態變量來跟蹤卡片是否處于展開狀態:
@Entry
@Component
struct ToggleCard {@State isExpanded: boolean = false // 控制卡片展開/收起狀態build() {Column() {Text('卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 一個帶展開按鈕的卡片Column() {Row() {Text('個人信息').fontSize(16).fontWeight(FontWeight.Medium)Blank()Button() {Image($r('sys.media.ohos_ic_public_arrow_down')).width(24).height(24).fillColor('#3F72AF')}.width(36).height(36).backgroundColor(Color.Transparent).onClick(() => {this.isExpanded = !this.isExpanded // 點擊按鈕切換狀態})}.width('100%').justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)// 根據展開狀態條件渲染內容if (this.isExpanded) {Text('這是展開后顯示的內容,包含詳細信息。').fontSize(14).margin({ top: 8 })}}.width('90%').padding(16).backgroundColor('#ECF2FF').borderRadius(12).margin({ top: 20 })}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
現在我們添加了一個@State
狀態變量isExpanded
,并在按鈕的onClick
事件中切換它的值。同時,我們使用if
條件語句根據isExpanded
的值決定是否顯示卡片的詳細內容。
步驟4:添加基本動畫效果
接下來,我們將為卡片的展開/收起添加動畫效果,讓交互更加流暢自然。HarmonyOS提供了兩種主要的動畫實現方式:
- animation屬性:直接應用于組件的聲明式動畫
- animateTo函數:通過改變狀態觸發的命令式動畫
首先,我們使用這兩種方式來實現箭頭旋轉和內容展開的動畫效果:
@Entry
@Component
struct ToggleCard {@State isExpanded: boolean = false// 切換卡片展開/收起狀態toggleCard() {// 使用animateTo實現狀態變化的動畫animateTo({duration: 300, // 動畫持續時間(毫秒)curve: Curve.EaseOut, // 緩動曲線onFinish: () => {console.info('卡片動畫完成') // 動畫完成回調}}, () => {this.isExpanded = !this.isExpanded // 在動畫函數中切換狀態})}build() {Column() {Text('卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 帶動畫效果的卡片Column() {Row() {Text('個人信息').fontSize(16).fontWeight(FontWeight.Medium)Blank()Button() {Image($r('sys.media.ohos_ic_public_arrow_down')).width(24).height(24).fillColor('#3F72AF').rotate({ angle: this.isExpanded ? 180 : 0 }) // 根據狀態控制旋轉角度.animation({ // 為旋轉添加動畫效果duration: 300,curve: Curve.FastOutSlowIn})}.width(36).height(36).backgroundColor(Color.Transparent).onClick(() => this.toggleCard()) // 調用切換函數}.width('100%').justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)if (this.isExpanded) {Column() {Text('這是展開后顯示的內容,包含詳細信息。').fontSize(14).layoutWeight(1)}.animation({ // 為內容添加動畫效果duration: 300,curve: Curve.EaseOut}).height(80) // 固定高度便于觀察動畫效果.width('100%')}}.width('90%').padding(16).backgroundColor('#ECF2FF').borderRadius(12).margin({ top: 20 })}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
在這個版本中,我們添加了兩種動畫實現:
- 使用
animateTo
函數來實現狀態變化時的動畫效果 - 使用
.animation()
屬性為箭頭旋轉和內容展示添加過渡動畫
這兩種動畫方式的區別:
- animation屬性:簡單直接,適用于屬性變化的過渡動畫
- animateTo函數:更靈活,可以一次性動畫多個狀態變化,有完成回調
步驟5:擴展為多卡片結構
現在讓我們擴展代碼,實現多個可獨立展開/收起的卡片:
// 定義卡片數據接口
interface CardInfo {title: stringcontent: stringcolor: string
}@Entry
@Component
struct ToggleCard {// 使用數組管理多個卡片的展開狀態@State cardsExpanded: boolean[] = [false, false, false]// 卡片數據private cardsData: CardInfo[] = [{title: '個人信息',content: '這是個人信息卡片的內容區域,可以放置用戶的基本信息,如姓名、年齡、電話等。',color: '#ECF2FF'},{title: '支付設置',content: '這是支付設置卡片的內容區域,可以放置用戶的支付相關信息,包括支付方式、銀行卡等信息。',color: '#E7F5EF'},{title: '隱私設置',content: '這是隱私設置卡片的內容區域,可以放置隱私相關的設置選項,如賬號安全、數據權限等內容。',color: '#FFF1E6'}]// 切換指定卡片的展開/收起狀態toggleCard(index: number) {animateTo({duration: 300,curve: Curve.EaseOut,onFinish: () => {console.info(`卡片${index}動畫完成`)}}, () => {// 創建新數組并更新特定索引的值let newExpandedState = [...this.cardsExpanded]newExpandedState[index] = !newExpandedState[index]this.cardsExpanded = newExpandedState})}build() {Column() {Text('多卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 使用ForEach遍歷卡片數據,創建多個卡片ForEach(this.cardsData, (card: CardInfo, index: number) => {// 卡片組件Column() {Row() {Text(card.title).fontSize(16).fontWeight(FontWeight.Medium)Blank()Button() {Image($r('sys.media.ohos_ic_public_arrow_down')).width(24).height(24).fillColor('#3F72AF').rotate({ angle: this.cardsExpanded[index] ? 180 : 0 }).animation({duration: 300,curve: Curve.FastOutSlowIn})}.width(36).height(36).backgroundColor(Color.Transparent).onClick(() => this.toggleCard(index))}.width('100%').justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)if (this.cardsExpanded[index]) {Column() {Text(card.content).fontSize(14).layoutWeight(1)}.animation({duration: 300,curve: Curve.EaseOut}).height(80).width('100%')}}.padding(16).borderRadius(12).backgroundColor(card.color).width('90%').margin({ top: 16 })})}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
在這個版本中,我們添加了以下改進:
- 使用
interface
定義卡片數據結構 - 創建卡片數據數組和對應的展開狀態數組
- 使用
ForEach
循環創建多個卡片 - 修改
toggleCard
函數接受索引參數,只切換特定卡片的狀態
步驟6:添加滾動容器和全局控制按鈕
最后,我們添加滾動容器和全局控制按鈕,完善整個頁面功能:
// 定義卡片數據接口
interface CardInfo {title: stringcontent: stringcolor: string
}@Entry
@Component
struct ToggleCard {// 使用數組管理多個卡片的展開狀態@State cardsExpanded: boolean[] = [false, false, false, false]// 卡片數據@State cardsData: CardInfo[] = [{title: '個人信息',content: '這是個人信息卡片的內容區域,可以放置用戶的基本信息,如姓名、年齡、電話等。點擊上方按鈕可以收起卡片。',color: '#ECF2FF'},{title: '支付設置',content: '這是支付設置卡片的內容區域,可以放置用戶的支付相關信息,包括支付方式、銀行卡等信息。點擊上方按鈕可以收起卡片。',color: '#E7F5EF'},{title: '隱私設置',content: '這是隱私設置卡片的內容區域,可以放置隱私相關的設置選項,如賬號安全、數據權限等內容。點擊上方按鈕可以收起卡片。',color: '#FFF1E6'},{title: '關于系統',content: '這是關于系統卡片的內容區域,包含系統版本、更新狀態、法律信息等內容。點擊上方按鈕可以收起卡片。',color: '#F5EDFF'}]// 切換指定卡片的展開/收起狀態toggleCard(index: number) {animateTo({duration: 300,curve: Curve.EaseOut,onFinish: () => {console.info(`卡片${index}動畫完成`)}}, () => {// 創建新數組并更新特定索引的值let newExpandedState = [...this.cardsExpanded]newExpandedState[index] = !newExpandedState[index]this.cardsExpanded = newExpandedState})}build() {Column({ space: 20 }) {Text('多卡片展開/收起示例').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 20 })// 使用滾動容器,以便在內容較多時可以滾動查看Scroll() {Column({ space: 16 }) {// 使用ForEach遍歷卡片數據,創建多個卡片ForEach(this.cardsData, (card: CardInfo, index: number) => {// 卡片組件Column() {Row() {Text(card.title).fontSize(16).fontWeight(FontWeight.Medium)Blank()Button() {Image($r('sys.media.ohos_ic_public_arrow_down')).width(24).height(24).fillColor('#3F72AF').rotate({ angle: this.cardsExpanded[index] ? 180 : 0 }).animation({duration: 300,curve: Curve.FastOutSlowIn})}.width(36).height(36).backgroundColor(Color.Transparent).onClick(() => this.toggleCard(index))}.width('100%').justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)if (this.cardsExpanded[index]) {Column({ space: 8 }) {Text(card.content).fontSize(14).layoutWeight(1)}.animation({duration: 300,curve: Curve.EaseOut}).height(100).width('100%')}}.padding(16).borderRadius(12).backgroundColor(card.color).width('100%')// 添加陰影效果增強立體感.shadow({radius: 4,color: 'rgba(0, 0, 0, 0.1)',offsetX: 0,offsetY: 2})})// 底部間距Blank().height(20)}.alignItems(HorizontalAlign.Center)}.align(Alignment.Top).padding(20).layoutWeight(1)// 添加底部按鈕控制所有卡片Row({ space: 20 }) {Button('全部展開').width('40%').onClick(() => {animateTo({duration: 300}, () => {this.cardsExpanded = this.cardsData.map((_: CardInfo) => true)})})Button('全部收起').width('40%').onClick(() => {animateTo({duration: 300}, () => {this.cardsExpanded = this.cardsData.map((_: CardInfo) => false)})})}.margin({ bottom: 30 })}.width('100%').height('100%').backgroundColor('#F5F5F5').alignItems(HorizontalAlign.Center).expandSafeArea()}
}
這個最終版本添加了以下功能:
- 使用
Scroll
容器,允許內容超出屏幕時滾動查看 - 添加"全部展開"和"全部收起"按鈕,使用
map
函數批量更新狀態 - 使用
space
參數優化布局間距 - 添加陰影效果增強卡片的立體感
三、關鍵技術點講解
1. 狀態管理
在HarmonyOS的ArkUI框架中,@State
裝飾器用于聲明組件的狀態變量。當狀態變量改變時,UI會自動更新。在這個示例中:
- 對于單個卡片,我們使用
isExpanded
布爾值跟蹤其展開狀態 - 對于多個卡片,我們使用
cardsExpanded
數組,數組中的每個元素對應一個卡片的狀態
更新數組類型的狀態時,需要創建一個新數組而不是直接修改原數組,這樣框架才能檢測到變化并更新UI:
let newExpandedState = [...this.cardsExpanded] // 創建副本
newExpandedState[index] = !newExpandedState[index] // 修改副本
this.cardsExpanded = newExpandedState // 賦值給狀態變量
2. 動畫實現
HarmonyOS提供了兩種主要的動畫實現方式:
A. animation屬性(聲明式動畫)
直接應用于組件,當屬性值變化時自動觸發動畫:
.rotate({ angle: this.isExpanded ? 180 : 0 }) // 屬性根據狀態變化
.animation({ // 動畫配置duration: 300, // 持續時間(毫秒)curve: Curve.FastOutSlowIn, // 緩動曲線delay: 0, // 延遲時間(毫秒)iterations: 1, // 重復次數playMode: PlayMode.Normal // 播放模式
})
B. animateTo函數(命令式動畫)
通過回調函數中改變狀態值來觸發動畫:
animateTo({duration: 300, // 持續時間curve: Curve.EaseOut, // 緩動曲線onFinish: () => { // 動畫完成回調console.info('動畫完成')}
}, () => {// 在這個函數中更改狀態值,這些變化將以動畫方式呈現this.isExpanded = !this.isExpanded
})
3. 條件渲染
使用if
條件語句實現內容的動態顯示:
if (this.cardsExpanded[index]) {Column() {// 這里的內容只在卡片展開時渲染}
}
4. 數據驅動的UI
通過ForEach
循環根據數據動態創建UI元素:
ForEach(this.cardsData, (card: CardInfo, index: number) => {// 根據每個數據項創建卡片
})
四、動畫曲線詳解
HarmonyOS提供了多種緩動曲線,可以實現不同的動畫效果:
- Curve.Linear:線性曲線,勻速動畫
- Curve.EaseIn:緩入曲線,動畫開始慢,結束快
- Curve.EaseOut:緩出曲線,動畫開始快,結束慢
- Curve.EaseInOut:緩入緩出曲線,動畫開始和結束都慢,中間快
- Curve.FastOutSlowIn:標準曲線,類似Android標準曲線
- Curve.LinearOutSlowIn:減速曲線
- Curve.FastOutLinearIn:加速曲線
- Curve.ExtremeDeceleration:急緩曲線
- Curve.Sharp:銳利曲線
- Curve.Rhythm:節奏曲線
- Curve.Smooth:平滑曲線
- Curve.Friction:摩擦曲線/阻尼曲線
在我們的示例中:
- 使用
Curve.FastOutSlowIn
為箭頭旋轉提供更自然的視覺效果 - 使用
Curve.EaseOut
為內容展開提供平滑的過渡
五、常見問題與解決方案
-
動畫不流暢:可能是因為在動畫過程中執行了復雜操作。解決方法是將復雜計算從動畫函數中移出,或者使用
onFinish
回調在動畫完成后執行。 -
條件渲染內容閃爍:為條件渲染的內容添加
.animation()
屬性可以實現平滑過渡。 -
卡片高度跳變:為卡片內容設置固定高度,或者使用更復雜的布局計算動態高度。
-
多卡片狀態管理復雜:使用數組管理多個狀態,并記得創建數組副本而不是直接修改原數組。
六、擴展與優化
你可以進一步擴展這個效果:
- 自定義卡片內容:為每個卡片添加更豐富的內容,如表單、圖表或列表
- 記住展開狀態:使用持久化存儲記住用戶的卡片展開偏好
- 添加手勢交互:支持滑動展開/收起卡片
- 添加動態效果:比如展開時顯示陰影或改變背景
- 優化性能:對于非常多的卡片,可以實現虛擬列表或懶加載
七、總結
通過本教程,我們學習了如何在HarmonyOS中實現卡片展開/收起效果,掌握了ArkUI中狀態管理和動畫實現的核心技巧。關鍵技術點包括:
- 使用
@State
管理組件狀態 - 使用
.animation()
屬性和animateTo()
函數實現動畫 - 使用條件渲染動態顯示內容
- 實現數據驅動的UI創建
- 為多個卡片獨立管理狀態
這些技術不僅適用于卡片展開/收起效果,也是構建其他復雜交互界面的基礎。
希望這篇 HarmonyOS Next 教程對你有所幫助,期待您的 👍點贊、💬評論、🌟收藏 支持。