[譯]玩轉 Android Paths

玩轉 Paths

我最近幫別人實現了一個 app 裏面英雄人物的動畫。然而,我如今還不能把這個動畫分享給大家。但我想分享在實現它的過程當中學到的東西。在這篇文章中,我將回顧如何重現這些由 Dave ‘beesandbombs’ Whyte 展現的迷人動畫,其中演示了不少同樣的實現技巧。html

beesandbombs 展現的多邊形繞圈前端

當我看到這個時(對熟悉我工做的人來講可能不是很驚訝),第一想法是使用 AnimatedVectorDrawable (下文會簡稱爲 AVD)。AVD 很好用,但不是適用全部的狀況 —— 特別是咱們有以下的需求的話:android

  • 我知道咱們須要畫一個多邊形,但還沒卻肯定具體要畫哪一個形狀。AVD 是須要預先設定參數的動畫,即改變形狀須要從新設置動畫。
  • 關於動畫進度追蹤的問題,咱們只想要繪製多邊形的一部分。AVD 是「義無反顧」地執行任務,若是動畫開始後,它會完整地執行完整個動畫,換句話說你不能取消它。
  • 咱們想要使另外一個物體繞着多邊形運動。這個固然也能夠經過 AVD 實現。但它仍是須要不少事前工做去計算想生成的軌跡。
  • 咱們想把繞多邊形物體的運動進度與多邊形的顯示分離開來,獨立控制。

所以我選擇用自定義 Drawable 來實現,其中包含多個 Path 對象。Path 是對圖形形狀的基本描繪(AVD 中實際也使用了 Path!),並且 Android Canvas 的 API也是藉助 Path 來生成各類有趣的效果。在實現一些效果以前,我想強烈推薦 Romain Guy 這篇寫得很好的文章,裏面展現的不少技巧就是我在本文所用到的:ios

Android Recipe #4, path tracinggit

極座標系

當定義 2d 形狀的時候,咱們一般在笛卡爾座標系 (x,y) 中進行定義。經過指定 x 軸和 y 軸上離原點的距離,來定義圖形形狀。而另外一個咱們可選用的極座標系,則是定義離原點的角度和半徑長度。github

笛卡爾座標系(左邊)vs 極座標系(右邊)canvas

咱們能夠經過這兩條公式進行極座標系和笛卡爾座標系之間的轉換:後端

val x = radius * Math.cos(angle);
val y = radius * Math.sin(angle);
複製代碼

我強烈推薦讀下面這篇文章以瞭解更多關於極座標系的內容:bash

極座標系app

爲了能生成規則的多邊形(例如每一個內角的度數相同),極座標系能起到很是大的做用。爲了生成想要的邊數,你能夠經過計算求出對應的度數(由於內角度數和是 360 度),而後藉助同一個半徑,再利用這個度數的多個倍數關係去描繪出每一個點。 你能夠用圖形 API 將這些點座標轉化爲笛卡爾座標。下面是一個經過給定的邊數和半徑生成多邊形 Path 的函數:

fun createPath(sides: Int, radius: Float): Path {
  val path = Path()
  val angle = 2.0 * Math.PI / sides
  path.moveTo(
      cx + (radius * Math.cos(0.0)).toFloat(),
      cy + (radius * Math.sin(0.0)).toFloat())
  for (i in 1 until sides) {
    path.lineTo(
        cx + (radius * Math.cos(angle * i)).toFloat(),
        cy + (radius * Math.sin(angle * i)).toFloat())
    }
  path.close()
  return path
}
複製代碼

因此爲了生成想要的多邊形組合,咱們建立了一個有不一樣邊數、半徑和顏色的多邊形 list 集合。Polygon 是一個持有這些信息和計算相應 Path 的類:

private val polygons = listOf(
  Polygon(sides = 3, radius = 45f, color = 0xffe84c65.toInt()),
  Polygon(sides = 4, radius = 53f, color = 0xffe79442.toInt()),
  Polygon(sides = 5, radius = 64f, color = 0xffefefbb.toInt()),
  ...
)
複製代碼

有效的 path 繪製

繪製一個 Path 只需簡單地調用 Canvas.drawPath(path, paint) 可是 Paint 類的參數支持 PathEffect,藉助這個咱們能夠去更改 path 被繪製時的效果。 例如咱們可使用 CornerPathEffect 去把咱們的多邊形的各個角圓滑化處理或者是用 DashPathEffect 去分段地畫出 Path(虛線效果,譯者注)(關於這個技巧的更多細節,請閱讀前面提到的那篇 Path tracing 文章 ):

另一種畫分段 path 的方法是使用 PathMeasure#getSegment,它能複製 path 的某一部分到一個新的 Path 對象。我是直接使用了能畫出虛線的方法,就像本身改變了繪製的時間間隔和分段繪製實現的效果同樣。

經過暴露這些控制 drawable 特性的參數,咱們能夠很容易地生成動畫:

object PROGRESS : FloatProperty<PolygonLapsDrawable>("progress") {
  override fun setValue(pld: PolygonLapsDrawable, progress: Float) {
    pld.progress = progress
  }
  override fun get(pld: PolygonLapsDrawable) = pld.progress
}

...

ObjectAnimator.ofFloat(polygonLaps, PROGRESS, 0f, 1f).apply {
  duration = 4000L
  interpolator = LinearInterpolator()
  repeatCount = INFINITE
  repeatMode = RESTART
}.start()
複製代碼

例如,這是繪製同心圓多邊形 path 過程的不一樣動畫效果:

吸附在 path 上

爲了繪製某個沿着 path 的物體,咱們可使用 PathDashPathEffect. 這會把另外一個 Path 沿着某條 path 「點印」在它上面,例如像這樣以藍色圓形形狀沿着一個多邊形的邊點印在上面:

PathDashPathEffect 接收 advancephase 兩個參數 —— 分別對應每一個 stamp(繪製在 path 上面的物體,譯者注)之間的間距和繪製第一個 stamp 在 path 上的偏移量。經過把每一個 stamp 的間距設置爲和整個 path 的長度同樣(經過 PathMeasure#getLength 獲取), 咱們就能夠只繪製出一個 stamp。而後再經過不斷改變偏移量,(偏移量是由 dotProgress 範圍 [0, 1] 控制)咱們就能夠實現只有一個 stamp 沿着 path 在運動的動畫效果。

val phase = dotProgress * polygon.length
dotPaint.pathEffect = PathDashPathEffect(pathDot, polygon.length,
    phase, TRANSLATE)
canvas.drawPath(polygon.path, dotPaint)
複製代碼

咱們如今有生成咱們圖形的全部要素。經過添加另外一個參數,就是每一個點在每一個多邊形上所對應的第幾「圈」的圈數,每一個點會完成對應的繞圈動畫。能生成像這樣的效果:

經過 Android drawable 實現本來 gif 的效果

你能夠經過下面的連接得到這個 drawable 的源碼: gist.github.com/nickbutcher…

展現不一樣的效果

大家可能已經注意到 PathDashPathEffect 構造方法中最後的參數:Style。這個枚舉類控制在 path 上面的 stamp 在每一個位置上是如何被繪製的。爲了展現這個參數的使用,下面的例子使用了一個三角形 stamp 代替圓形,去展現平移(translate)旋轉(rotate)的效果差異:

比較平移旋轉效果的異同

注意到使用 translate 效果時,三角形 stamp 方向老是相同的(箭頭方向指向左)而若是是 rotate 效果的話,三角形會旋轉自身保持在處於 path 的切線方向上。

還有一種 類型 叫作 morph,能讓 stamp 平穩變換。爲了展現這個效果,我把 stamp 變成了以下的一條線段。請觀察當通過角落時,線段是如何彎曲的:

當PathDashPathEffect.Style的類型爲 MORPH

有趣的是,某些狀況下,在 path 的開頭或緊密的角落,stamp 的形狀有點扭曲。

提醒一點你可使用 ComposePathEffect 去組合多種 PathEffect 在一塊兒,經過將 PathDashPathEffectCornerPathEffect 一塊兒組合使用,能夠實現讓 stamp 在有圓滑角落的 path 上運動。

使用正切

上面咱們所討論的是關於如何生成多邊形的繞圈組合,而我最初的需求實際上還要麻煩點。使用 PathDashPathEffect 的缺點是隻能應用一種單一的形狀和顏色。我本身的做品須要有更精巧的標記(marker,即 stamp,譯者注),因此我用一種比點印在 path 上更好的辦法。我使用了 Drawable 而且計算給定一個進度的話,沿着 Path 標記須要在哪一個地方繪製出來。

沿着 path 移動 VectorDrawable

爲了實現這個效果,我再次使用 PathMeasure 類,它提供了 getPosTan 方法獲取位置座標,和沿着某個 Path 給定長度時的正切值。經過這樣(涉及到一點數學),咱們能夠平移和旋轉畫布,從而讓咱們的標記繪製在正確的位置和方向上。

pathMeasure.setPath(polygon.path, false)
pathMeasure.getPosTan(markerProgress * polygon.length, pos, tan)
canvas.translate(pos[0], pos[1])
val angle = Math.atan2(tan[1].toDouble(), tan[0].toDouble())
canvas.rotate(Math.toDegrees(angle).toFloat())
marker.draw(canvas)
複製代碼

找到你的 path

但願這篇文章可以說明自定義 drawable 的同時去建立和操做 path 對於生成有趣的圖形效果是多麼有用。 編寫一個自定義 drawable,在單獨更改各部分的動畫效果這方面有很靈活的控制。這個方法也能讓你動態更改數值,而不用須要預先就設定好整個動畫。期待大家經過 Android 的 Path API 和其餘內置效果實現更多新奇的效果,而這些工具早在 API 1 的時候就已經可使用了。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索