LocationMarker是运动轨道上Start、End, 以及整公里点上笔者自定义制作的一个MarkerView, 其时之所以没有用规划给的icon是这个MarkerView里需要填充动态的数字,自定义的话自主性比较大些也方面做动画,之前的Android 传统自定义View的完成可以看这篇文章介绍 运动App自定义LocationMarker。

这儿先看下gif动图:

Compose自定义View——LocationMarkerView

LocationMarkerView图的制作

制作方面基本没有太多的逻辑,经过Compose的自定义制作Canvas() 制作 一个构建的Path,生成View的Path其实是首要的完成过程。

Canvas(modifier = Modifier.size(0.dp)){
 drawPath(AndroidPath(markerViewPath), color = color)
 drawPath(AndroidPath(bottomOval), color = colorOval)
}

这儿Compose的path,还有好些接口对不上以及缺少API,所以经过AndroidPath(nativepath)接口进行转化进行制作,bottomOval是 Start、End点底部暗影的Path。生成markerViewPath以及bottomOval的逻辑都在LocationMarker类中,LocationMarker首要包含了上下两套点 p1、p3(HPoint), 左右两套点p2、p4(VPoint), 以及制作View的参数属性调集类MarkerParams.

获取markerViewPath, 首先给p1、p3(HPoint),p2、p4(VPoint)中8个点设置Value值,circleModel(radius),然后从底部p1底部点逆时针转圈依次调用三阶贝塞尔函数接口,终究close完成水滴倒置状况的Path,见完成:

fun getPath(radius: Float): Path{
 circleModel(radius)
 val path = Path()
 p1.setYValue(p1.y + radius * 0.2f * 1.05f) //设置 p1 底部左右两个点的y值
 p1.y += radius * 0.2f * 1.05f //设置 p1 自己的y值
 path.moveTo(p1.x, p1.y)
 path.cubicTo(p1.right.x, p1.right.y, p2.bottom.x, p2.bottom.y, p2.x, p2.y)
 path.cubicTo(p2.top.x, p2.top.y, p3.right.x, p3.right.y, p3.x, p3.y)
 path.cubicTo(p3.left.x, p3.left.y, p4.top.x, p4.top.y, p4.x, p4.y)
 path.cubicTo(p4.bottom.x, p4.bottom.y, p1.left.x, p1.left.y, p1.x, p1.y)
 path.close()
 val circle = Path()
 circle.addCircle(p3.x, p3.y + radius, markerParams.circleRadius.value, Path.Direction.CCW)
 path.op(circle, Path.Op.DIFFERENCE)
 return path
}

拿到相应的Path后,在Composeable函数里进行如上所述的制作Path即可:

val locationMarker = LocationMarker(markerParams)
val markerViewPath = locationMarker.getPath(markerParams.radius.value)
val bottomOval = locationMarker.getBottomOval()
val color = colorResource(id = markerParams.wrapperColor)
val colorOval = colorResource(R.color.location_bottom_shader)
​
Canvas(modifier = Modifier.size(0.dp)){
 drawPath(AndroidPath(markerViewPath), color = color)
 drawPath(AndroidPath(bottomOval), color = colorOval)
}

制作整公里的文字

Compose的Canvas 里现在的Version并不支持drawText的制作,不过开放了一个调用原始drawText的转化API, 原始的drawText 是需要Paint参数的, 一起依靠Paint来核算Text 对应RectF的Height值,这儿Paint()是Compose的一个Paint,需要调用asFrameworkPaint() 进行转化

val paint = Paint().asFrameworkPaint().apply {
 setColor(-0x1)
 style = android.graphics.Paint.Style.FILL
 strokeWidth = 1f
 isAntiAlias = true
 typeface = Typeface.DEFAULT_BOLD
 textSize = markerParams.txtSize.toFloat()
}

核算Text 制作依靠的RectF,并将rectF.left作为drawText的X值,一起核算drawText的基线 baseLineY,终究传入nativeCanvas.drawText() 接口进行制作。

val rectF = createTextRectF(locationMarker, paint, markerParams)
val baseLineY = getTextBaseY(rectF, paint)
Canvas(modifier = Modifier.size(0.dp)){
 drawIntoCanvas {
  it.nativeCanvas.drawText(markerParams.markerStr, rectF.left, baseLineY, paint)
  }
}

drawText获取制作基线 baseLineY的东西类办法:

fun getTextBaseY(rectF: RectF, paint: Paint): Float {
  val fontMetrics = paint.fontMetrics
  return rectF.centerY() - fontMetrics.top / 2 - fontMetrics.bottom / 2
}

增加动画

这儿简略的用一个扩大的动画完成,跟原始的高德地图、Mapbox地图的一个growth过程的一个动画有些差距的,暂且先这样完成吧。首先是定义两个radius相关的State目标,具体来说是Proxy, 以及一个动画成长的大小控制的Float的变量Fraction,再经过自定义animateDpAsState作为 animation值的目标,终究给到MarkParams作为参数,animation值的改变,会导致MarkParams的改变,终究导致Recompose,构成动画。

 val circleRadius by rememberSaveable{ mutableStateOf(25) }
 val radius by rememberSaveable{ mutableStateOf(60) }
 var animatedFloatFraction by remember { mutableStateOf(0f) }
 val radiusDp by animateDpAsState(
  targetValue = (radius * animatedFloatFraction).dp,
  animationSpec = tween(
   durationMillis = 1000,
   delayMillis = 500,
   easing = LinearOutSlowInEasing
   )
  )
​
 val circleRadiusDp by animateDpAsState(
  targetValue = (circleRadius * animatedFloatFraction).dp,
  animationSpec = tween(
   durationMillis = 1000,
   delayMillis = 500,
   easing = LinearOutSlowInEasing
   )
  )
​
 val markerParams by remember {
  derivedStateOf { MarkerParams(radiusDp, circleRadiusDp, wrapperColor = wrapperColor) }
  }
  

Compose 自定义View LocationMarkerView 首要经过drawPath,以及调用原生的drawText, 终究增加了一个scale相似的动画完成,终究完成运动轨道里的一个小小的View的完成。

代码见:github.com/yinxiucheng… 下的CustomerComposeView