循序漸進的講解用Android Jetpack Compose 寫一個B站“一鍵三連”按鈕動畫, 願新的一年好運連連

語言: CN / TW / HK

1. 知識拆解

從本文你可以學習到以下知識點:

  • 如何快速學會用Compose進行佈局
  • 理解Composable函數中數據驅動UI的編程思想, 理解remember函數的作用
  • 理解函數副作用, 編寫動畫

1.1 佈局

“一鍵三連”由三個部分構成: 點贊及點贊數、投幣及投幣數和收藏及收藏數, 三個部分橫向排布, 因此可以用Row來包裹

Row {  點贊組件  投幣組件  收藏組件 }

以點贊為例, 它是上下結構, 且數字對齊於圖標, 在傳統Android View體系中, 我們很快會想到約束佈局然後使其左邊對齊左邊👈, 右邊對其右邊👉, 慶幸的是Compose中也有constraintlayout-compose庫, 所以我們引入該庫

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

該庫與我們以前使用約束佈局時的思想是一樣的:

  1. ConstraintLayout包裹子元素
  2. 給每個子元素生成id, 此處叫createRefs創建引用
  3. 將子元素對其parent, 子元素之間再按需對齊

ConstraintLayout {  // 給點贊圖標、數量文本創建引用, 此處使用瞭解構聲明語法  val (refThumb, refCounter) = createRefs()  // 將點贊圖標引用的左、上、右對齊`parent`的左、上、右邊  Icon(modifier = Modifier.constrainAs(refThumb) {     start.linkTo(parent.start)      top.linkTo(parent.top)      end.linkTo(parent.end)               })  // 將文本引用的左右兩邊對其`parent`,將頂部對齊圖標的底部  Text(modifier = Modifier.constrainAs(refCounter) {                    start.linkTo(parent.start)                    end.linkTo(parent.end)                    top.linkTo(refThumb.bottom)                 }) }

再如法炮製投幣和收藏組件,這樣"一鍵三連"就佈局好了

Row {  ConstraintLayout {    Icon()    Text() }  ConstraintLayout {    Icon()    Text() }  ConstraintLayout {    Icon()    Text() } }

1.2 用狀態驅動UI

在我們傳統的Android View體系中,我們給TextView設置文本一般這麼做:

  1. 找到這個view: textView = findViewId(id)
  2. 設置view的屬性: textView.setText("67")

而在compose UI編程中,狀態指明瞭UI的當前屬性,狀態改變UI隨之改變。用一個表達式表示為:

$$UI = composable(state)$$

因此,

  1. 我們通過remember聲明一個狀態,它表示會變化的文本內容, 並且它可以跨越composable函數的重組階段而不被重新初始化(這是關鍵):

var thumbCount by remember { mutableStateOf(66) }

  1. 將狀態設置給Text:

Text(thumbCount.toString())

  1. 改變狀態,composable函數會進行重組,從而自動改變文本內容

Text(thumbCount.toString(), modifier = Modifier.clickable { // 點擊時將數量+1                      thumbCount = thumbCount + 1                 })

1.3 設置長按事件,繪製動畫

“一鍵三連”時,長按時開始出現圓弧進度條,並且隨着時間圓弧掃過的角度從0-360°, 直到變成完整的圓圈⭕️,它有以下屬性

  1. 圓弧增長是連續的,或者説看似連續的, 每一小段時間就增長一點圓弧角度
  2. 圓弧角度是一個狀態,因為他不能因為函數重組而被重新初始化
  3. 動畫是可以被打斷的且被反轉的,鬆手後不再增加角度而是減小角度,因此圓弧角度的變化可以被放在一個協程中運行, 因為它可以很容易被取消並重新開始

``` // 三連進度 從0到-360度,逆時針 var hitProgress by remember { mutableStateOf(0) } var hitJob by remember { mutableStateOf(null) } ... // 觸摸事件: ACTION_DOWN hitJob?.cancel() hitJob = scope.async { // 手指按下後,逐步減少hitProgress,使圓弧角度逆時針增加 while (hitProgress > -360) { delay(15) hitProgress -= 4 } } ... // 觸摸事件: ACTION_UP hitJob?.cancel() hitJob = scope.async { // 手指抬起時, 增加hitProgress,使圓弧逐步縮短 while (hitProgress < 0) { delay(8) hitProgress += 4 } }

...

// 畫一段圓弧,從-90度開始,掃過hitProgress的角度, 圓弧的弧長會自動跟隨hitProgress的變化而變化 drawArc( startAngle = -90f, sweepAngle = hitProgress.toFloat(), ) ```

2. 總結

通過以上的例子,我們可以發現,compose相比傳統view在佈局、動畫方面會更為快速便捷。但也與view體系有很大的不同,主要體現在:

  1. 無法拿到view對象,來改變自身屬性,而是通過狀態依賴來驅動自身屬性
  2. 動畫的實現通過函數副作用來實現,它又與協程聯繫緊密

源碼地址: Github