欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Compose自定義View實現(xiàn)繪制Rainbow運動三環(huán)效果

 更新時間:2023年02月14日 08:29:22   作者:cxy107750  
這篇文章主要為大家介紹了一個基于Compose自定義的一個Rainbow彩虹運動三環(huán),業(yè)務(wù)上類似于iWatch上的那個運動三環(huán),感興趣的小伙伴可以了解一下

本章節(jié)介紹的是一個基于Compose自定義的一個Rainbow彩虹運動三環(huán),業(yè)務(wù)上類似于iWatch上的那個運動三環(huán),不過這里實現(xiàn)的用的一個半圓去繪制,整個看起來像彩虹,三環(huán)的外兩層為卡路里跟步數(shù),最里層可設(shè)定為活動時間,站立次數(shù)。同樣地首先看一下gif動圖:

大致地介紹一下Rainbow的繪制過程,很明顯圖形分兩層,底層有個alpha為0.4f * 255的背景底,前景會依據(jù)具體的值的百分占比繪制一個角度的弧度環(huán),從外往里分三個Type的環(huán),每個環(huán)有前景跟背景,畫三次,需要每次對Canvas進(jìn)行一個translate,環(huán)的繪制邏輯放在了RainbowModel里了,前景加背景所以這個一共需要被調(diào)用6次。

@Composable
fun drawCircle(type: Int,
               fraction: Float,
               isBg: Boolean, modifier: Modifier){
  val colorResource = getColorResource(type)
  val color = colorResource(id = colorResource)
  Canvas(modifier = modifier.fillMaxSize()){
    val contentWidth = size.width
    val contentHeight = size.height
    val itemWidth = contentWidth / 7.2f
    val spaceWidth = itemWidth / 6.5f
    val rectF = createTargetRectF(type, itemWidth, spaceWidth, contentWidth, contentHeight)
    val space = if (type == RainbowConstant.TARGET_THIRD_TYPE) 
    spaceWidth/2.0f else spaceWidth
    val sweepAngel = fraction * 180
    val targetModel = createTargetModel(isBg, type, rectF, itemWidth, space, sweepAngel)
    println("drawRainbow width:${rectF.width()}, height${rectF.height()}")
    if (checkFractionIsSmall(fraction, type)) {
      val roundRectF = createRoundRectF(type, itemWidth, spaceWidth, contentHeight)
      drawRoundRect(
        color = color,
        topLeft = Offset(x = roundRectF.left, y = roundRectF.top),
        size = Size(roundRectF.width(), roundRectF.height()),
        cornerRadius = CornerRadius(spaceWidth / 2.0f, spaceWidth / 2.0f)
      )
    } else {
      withTransform({ translate(left = rectF.left, top = rectF.top) }) {
        targetModel.createComponents()
        targetModel.drawComponents(this, color, isBg)
      }
    }
  }
}

這里有個邊界需要處理,當(dāng)百分比比較小的時候繪制的一個RoundRectF, 而且不需要translate。

這里前景的三次調(diào)用做了個簡易的動畫,如上面的gif動圖所示:

val animator1 = remember{ Animatable(0f, Float.VectorConverter) }
val animator2 = remember{ Animatable(0f, Float.VectorConverter) }
val animator3 = remember{ Animatable(0f, Float.VectorConverter) }
?
val tweenSpec = tween<Float>(durationMillis = 1000, delayMillis = 600, easing = FastOutSlowInEasing)
LaunchedEffect(Unit){
  animator1.animateTo(targetValue = 0.5f, animationSpec = tweenSpec)
}
LaunchedEffect(Unit){
  animator2.animateTo(targetValue = 0.7f, animationSpec = tweenSpec)
}
LaunchedEffect(Unit){
  animator3.animateTo(targetValue = 0.8f, animationSpec = tweenSpec)
}
?
drawCircle(
  type = RainbowConstant.TARGET_FIRST_TYPE,
  fraction = animator1.value,
  isBg = false,
  modifier
)
drawCircle(
  type = RainbowConstant.TARGET_SECOND_TYPE,
  fraction = animator2.value,
  isBg = false,
  modifier
)
drawCircle(
  type = RainbowConstant.TARGET_THIRD_TYPE,
  fraction = animator3.value,
  isBg = false,
  modifier
)

Rainbow環(huán)的繪制

上面是Rainbow繪制的外層框架,然后每個Rainbow環(huán)的繪制的邏輯(這里沒有用SweepGradient,Compose里對應(yīng)的為brush 參數(shù), 直接用的單一的Color值)即上面的targetModel.drawComponents(this, color, isBg) 背后的邏輯。想必讀者都繪制過RoundRectF, 這里的RountF 弧形環(huán)是如何實現(xiàn)繪制的呢?整個的邏輯在RainbowModel里,這里把小圓角視為一個近似直角的扇形,所以一共有4個小扇形,然后除去4個小扇形,中間一個大的沒有圓角的弧形,外加內(nèi)層、外層出去圓角的小弧形,所以總共7個path:

private lateinit var centerCircle: Path
private lateinit var wrapperCircle: Path
private lateinit var innerCircle: Path
private lateinit var wrapperStartPath: Path
private lateinit var wrapperEndPath: Path
private lateinit var innerStartPath: Path
private lateinit var innerEndPath: Path

然后稍微簡單介紹下小扇形的繪制, 內(nèi)層跟外層不太一樣,通過構(gòu)建封閉的Path,所以需要用的圓角的曲線,這里近似地用二階Bezier代替,所以需要找它的Control點,這里直接用沒有沒有圓角情況下,直徑網(wǎng)外射出去跟圓角的交點,同樣外、內(nèi)的計算稍微不太一樣:

fun createCommonPoint(rectF: RectF, sweepAngel: Float): PointF {
  val radius = rectF.width() / 2
  val halfCircleLength = (Math.PI * radius).toFloat()
  val pathOriginal = Path()
  pathOriginal.moveTo(rectF.left, (rectF.top + rectF.bottom) / 2)
  pathOriginal.arcTo(rectF, 180f, 180f, false)
  val pathMeasure = PathMeasure(pathOriginal, false)
  val points = FloatArray(2)
  val pointLength = halfCircleLength * sweepAngel / 180f
  pathMeasure.getPosTan(pointLength, points, null)
  return PointF(points[0], points[1])
}
?
fun createEndPoint(rectF: RectF, sweepAngel: Float): PointF {
  val radius = rectF.width() / 2
  val halfCircleLength = (Math.PI * radius).toFloat()
  val pathOriginal = Path()
  pathOriginal.moveTo(rectF.right, (rectF.top + rectF.bottom) / 2)
  pathOriginal.arcTo(rectF, 0f, -180f, false)
  val pathMeasure = PathMeasure(pathOriginal, false)
  val points = FloatArray(2)
  val pointLength = halfCircleLength * sweepAngel / 180f
  pathMeasure.getPosTan(pointLength, points, null)
  return PointF(points[0], points[1])
}

借助PathMeasure通過計算 弧長跟半圓的一個Compare,計算弧長的endpoint, 這個點算作 小扇形的二階bezier的Control點,然后通過createQuadPath()來構(gòu)建小扇形。

fun createQuadPath(): Path {
  quadPath = Path()
  quadPath.apply {
  moveTo(startPointF.x, startPointF.y)
  quadTo(ctrlPointF.x, ctrlPointF.y, endPointF.x, endPointF.y)
  lineTo(centerPointF.x, centerPointF.y)
  close()
  }
  return quadPath
}

以下是在RainbowModel里計算wrapperStartPath、wrapperEndPath、innerStartPath、innerEndPath 具體的邏輯

private fun createInnerPath() {
  innerStartPath = Path()
  val startQuadModel = QuadModel()
  startQuadModel.centerPointF =
  startQuadModel.createCommonPoint(innerStartRectF, innerFixAngel)
  startQuadModel.ctrlPointF = startQuadModel.createCommonPoint(innerEndRectF, 0f)
  startQuadModel.startPointF =
  startQuadModel.createCommonPoint(innerEndRectF, innerFixAngel)
  startQuadModel.endPointF = startQuadModel.createCommonPoint(innerStartRectF, 0f)
  innerStartPath = startQuadModel.createQuadPath()
  val endQuadModel = QuadModel()
  endQuadModel.centerPointF =
  endQuadModel.createEndPoint(innerStartRectF, 180 - sweepAngel + innerFixAngel)
  endQuadModel.ctrlPointF = endQuadModel.createCommonPoint(innerEndRectF, sweepAngel)
  endQuadModel.startPointF = endQuadModel.createCommonPoint(innerStartRectF, sweepAngel)
  endQuadModel.endPointF =
  endQuadModel.createEndPoint(innerEndRectF, 180 - sweepAngel + innerFixAngel)
  innerEndPath = endQuadModel.createQuadPath()
}
?
private fun createWrapperPath() {
  val startQuadModel = QuadModel()
  startQuadModel.centerPointF =
  startQuadModel.createCommonPoint(wrapperEndRectF, wrapperFixAngel)
  startQuadModel.ctrlPointF = startQuadModel.createCommonPoint(wrapperStartRectF, 0f)
  startQuadModel.startPointF = startQuadModel.createCommonPoint(wrapperEndRectF, 0f)
  startQuadModel.endPointF =
  startQuadModel.createCommonPoint(wrapperStartRectF, wrapperFixAngel)
  wrapperStartPath = startQuadModel.createQuadPath()
  val endQuadModel = QuadModel()
  endQuadModel.centerPointF =
  endQuadModel.createEndPoint(wrapperEndRectF, 180 - sweepAngel + wrapperFixAngel)
  endQuadModel.ctrlPointF = endQuadModel.createCommonPoint(wrapperStartRectF, sweepAngel)
  endQuadModel.startPointF =
  endQuadModel.createEndPoint(wrapperStartRectF, 180 - sweepAngel + wrapperFixAngel)
  endQuadModel.endPointF = endQuadModel.createCommonPoint(wrapperEndRectF, sweepAngel)
  wrapperEndPath = endQuadModel.createQuadPath()
}

以上大致是小扇形的繪制邏輯,其中關(guān)鍵的一些點在于,因為它比較小所以直接用二階貝塞爾來代替圓弧,通過PathLength里計算任一sweepAngel下的二階Bezier的Control點。然后內(nèi)層跟外層的一些計算上數(shù)據(jù)幾何上的問題的處理,逆時針、順時針的注意,筆者也是在代碼過程中慢慢調(diào)試,然后修改變量等。

然后其它三個Path相對比較簡單,不做過多介紹了。

代碼同樣在https://github.com/yinxiucheng/compose-codelabs/ 下的CustomerComposeView 的rainbow的package 下面。

以上就是Compose自定義View實現(xiàn)繪制Rainbow運動三環(huán)效果的詳細(xì)內(nèi)容,更多關(guān)于Compose Rainbow運動三環(huán)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論