Android实现绘制LocationMarkerView图的示例代码
作者:cxy107750 发布时间:2023-01-14 07:55:29
LocationMarker是运动轨迹上Start、End, 以及整公里点上笔者自定义绘制的一个MarkerView, 当时之所以没有用设计给的icon是这个MarkerView里需要填充动态的数字,自定义的话自主性比较大些也方面做动画,之前的Android 传统自定义View的实现可以看这篇文章介绍 运动App自定义LocationMarker。
这里先看下gif动图:
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的实现。
代码见:https://github.com/yinxiucheng/compose-codelabs/ 下的CustomerComposeView
来源:https://juejin.cn/post/7197433837340901432


猜你喜欢
- 前言前段时间看到一道面试题:“main函数可以被重载么?”,当时就蒙圈了,怎么还会有这种面试题,现在
- 前言Spark Sql可以通过UDF来对DataFrame的Column进行自定义操作。在特定场景下定义UDF可能需要用到Spark Con
- 本文实例讲述了Android弹出窗口实现方法。分享给大家供大家参考,具体如下:直接上代码:/*** 弹窗--新手指引* @param cxt
- 简述每个项目从新建开始我们或多或少都会导入各种依赖库,如果项目中只有一个module的话,对于依赖库版本的管理很容易,但是多个module的
- C#是一门面向对象的语言,具有面向对象的基本特征,抽象、封装、继承、多态等性质。学习C#除了一些基本的语法,还得学习一些新的特性,比如说:泛
- 1. 启动 Redis Server启动 redis server,如下图所示,端口号 6379:2. 工程实例2.1 工程目录工程目录如下
- Java 方法执行时的动态分派和静态分派是 Java 实现多态的本质背景Java 的动态分派和静态分派也是 Java 方法的执行原理。 Ja
- 直接用idea clean install 进行打包maven项目时,如果没有进行设置会把测试文件也打包进去。想要忽略test文件将Mave
- 一、ObjectContext对象上下文Entity SQL 语言 - ADO.NET | Microsoft 官当文档ObjectCont
- @Value注解内使用SPEL自定义函数@Value("#{T(com.cheetah.provider.utils.String
- 一、问题描述有时候,我们会遇到在遍历List集合的过程中删除数据的情况。看着自己写的代码,感觉完全没有问题,但就是达不到预期的效果,这是为什
- 今天导入了一个模型,但是模型贴图丢失了,而且Inspector面板中处于不能编辑的状态虽然可以通过重新创建材质来替换,但是这样会生成一个新的
- 本文实例为大家分享了Springboot整合pagehelper分页展示的具体代码,供大家参考,具体内容如下一、添加依赖查找maven中pa
- 假如我们有一张banner_item表,现需要通过banner_id查出所有数据(查询List)@Datapublic class Bann
- //创建站点地图 private void Create
- MyBatis * 介绍MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是 * 功能。那么 * 拦截MyBatis
- 经常要检测某些IP地址范围段的计算机是否在线。有很多的方法,比如进入到网关的交换机上去查询、使用现成的工具或者编写一个简单的DOS脚本等等,
- 字符串的拼接,常使用到的大概有4种方式:1.直接使用"+"号2.使用String的concat方法3.使用StringB
- package com.famous.dark.util;import java.io.File;import java.io.FileFi
- 文档中的设置有序或无序列表是一种反应内容上下级关系或者内容相同属性的方式,与单纯的文字叙述相比,它能有效增强文档内容的条理性,突出重点。因此