欢迎访问 如意编程网!

如意编程网

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

安卓自定义View-类似操场跑道

发布时间:2024/5/15 编程问答 2 豆豆
如意编程网 收集整理的这篇文章主要介绍了 安卓自定义View-类似操场跑道 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

先上图:


由于是新手,代码通篇较简单,啰嗦之处还请见谅
代码仅为关键部分,不是完整项目案例,更多功能可以自行拓展,这里只是记录一下并且给大佬们仅提供思路

核心思路 如图:

addArc:用于添加一个圆弧路径到当前路径中
arcTo :与 addArc 方法类似,也是用于添加一个圆弧路径到当前路径中,但与 addArc 不同的是,arcTo 方法可以指定圆弧的起点和终点,而不会自动连接这两个点 如上图所示
lineTo :用于从当前路径的最后一个点绘制一条直线到指定的点
PathMeasure :
PathMeasure 类的主要作用是计算路径的长度,以及用于获取路径上的坐标点。具体应用场景如下:

  • 绘制动画:可以使用 PathMeasure 获取路径的长度,然后在指定的时间内,通过改变绘制位置的距离和路径长度的比例,来实现路径动画的效果。

  • 滑动解锁:可以使用 PathMeasure 获取路径的长度,然后通过手势的滑动速度和路径长度的比例来判断是否达到解锁阈值,从而实现滑动解锁功能。

  • 曲线绘制:可以使用 PathMeasure 获取路径上的坐标点,然后通过这些坐标点来绘制曲线。

PathMeasure 类的常用方法如下:

  • PathMeasure(Path path, boolean forceClosed):创建一个用于测量给定路径的 PathMeasure 对象,其中 path 参数是要测量的路径对象,forceClosed 参数表示是否强制关闭路径。

  • getLength():获取路径的总长度。

  • getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo):从路径上获取一段路径段,并将其存储在另一个 Path 对象中,其中 startD 和 stopD 参数表示要获取路径的起始和结束位置,dst 参数表示存储路径的 Path 对象,startWithMoveTo 参数表示是否在起始点插入一个 moveTo 命令。

  • getPosTan(float distance, float[] pos, float[] tan):获取路径上指定距离的坐标位置与正切向量,并存储在相应的数组中,其中 distance 参数表示路径的距离位置,pos 数组存储路径上该位置的坐标,tan 数组存储路径上该位置的正切向量。

  • nextContour():移动到下一个(若存在)封闭子路径上,并返回是否成功移动。

可以根据需要使用上述方法来计算路径的长度、获取路径上的坐标点、绘制路径动画等等应用场景。

关于绘制文字的一些知识点,一张图片概括:
注意文字绘制 是以view的左上角为起点的,不是右下角

关于基线的知识可参考:链接: Android文字基线(Baseline)算法

下面是该View的全部代码

/*** @author: 听风* @date: 2023/5/29** 宽 高 比 2.3 :1 */ class TrackView(context: Context) : View(context) {constructor(context: Context, attrs: AttributeSet) : this(context) {lineSpace = dp2px(20)initPaint()}//内部线画笔 内部跑道线lateinit var innerPaint: Paint//中间线画笔 中间跑道线lateinit var centerPaint: Paint//外部线画笔 外部跑道线lateinit var outPaint: Paint//跑道背景色lateinit var bgPaint: Paint//中间文字画笔lateinit var centerTextPaint: Paint//用户名画笔lateinit var textPaint: Paint//名字背景画笔lateinit var mTextBgPaint: Paint//icon 画笔lateinit var iconPaint: Paint//已运动距离画笔lateinit var dstPaint: Paint//外部线pathlateinit var outPath: Path//中间线pathlateinit var centerPath: Path//中间运动片段pathprivate val dstPath = Path()//跑道背景pathlateinit var bgPath: Path//内部线pathlateinit var innerPath: Path//内部线颜色private val innerColor = Color.BLACK//中间线颜色private val centreColor = Color.GRAY//最外部线颜色private val outColor = Color.BLACK//跑道线之间间隔private var lineSpace = 0//内部矩形lateinit var innerRect: RectF //内部矩形lateinit var innerRectL: RectF //内部矩形lateinit var innerRectR: RectF//中间矩形lateinit var centerRect: RectF //中间矩形lateinit var centerRect1: RectF //中间矩形lateinit var centerRect2: RectF//外部矩形lateinit var outRect: RectF //外部矩形lateinit var outRect1: RectF //外部矩形lateinit var outRect2: RectF//测量中间文字用private val textBounds = Rect()//画布宽高private var mWidth = 0 //画布宽高private var mHeight = 0//private val mContext: Context? = null//是否初始化完成 防止重复测量private var inited = false//lateinit var icon: Bitmap//lateinit var startBp: Bitmap//起点图片坐标private val startPoint = Point()//测量已运动路径用var measure = PathMeasure()//中间跑道路径的实际长度var pathLength = 0fprivate val singleDistance = 0f //单人已运动距离private val sumDis = 400 //操场总距离//用来记录path上某距离处坐标var point = FloatArray(2)private fun initPaint() {innerPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = innerColorstyle = Paint.Style.STROKEstrokeWidth = dp2px(2).toFloat()}centerPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = centreColorstyle = Paint.Style.STROKEstrokeWidth = dp2px(1).toFloat()}//虚线 参数1:{虚线长度,虚线间隔} 参数2:开始的偏移量val dashPathEffect =DashPathEffect(floatArrayOf(dp2px(10).toFloat(),dp2px(5).toFloat()), 0f)centerPaint.pathEffect = dashPathEffectoutPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = outColorstyle = Paint.Style.STROKEstrokeWidth = dp2px(2).toFloat()}bgPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = Color.parseColor("#55ffffff")style = Paint.Style.STROKEstrokeWidth = (lineSpace * 2).toFloat()}centerTextPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = Color.BLUEstrokeWidth = sp2px(2).toFloat()textSize = sp2px(30).toFloat()}textPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = Color.BLACKtextSize = sp2px(10).toFloat()}mTextBgPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {color = -0x4c080809style = Paint.Style.FILL_AND_STROKEstrokeWidth = sp2px(10 + 4).toFloat()strokeCap = Paint.Cap.ROUND}iconPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = 1f}dstPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = dp2px(5).toFloat()strokeCap = Paint.Cap.ROUNDcolor = Color.BLUE}}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)//可能会被多次测量if (!inited) {mWidth = MeasureSpec.getSize(widthMeasureSpec)mHeight = MeasureSpec.getSize(heightMeasureSpec)inited = true}}override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)initRect()}private fun initRect() {//跑道距边缘的Padding//跑道距边缘的Paddingval leftPadding = dp2px(20)val rightPadding = dp2px(20)val topPadding = dp2px(10)val bottomPadding = dp2px(10)//画外面//画外面val outWidth = outPaint.strokeWidth.toInt()//最外面跑道线val x = outWidth / 2 + leftPaddingval y = outWidth / 2 + topPaddingval x1 = width - outWidth / 2 - rightPaddingval y1 = height - outWidth / 2 - bottomPaddingval outSpace = y1 - y //实际高度outRect = RectF(x.toFloat(), y.toFloat(), x1.toFloat(), y1.toFloat())//辅助矩形1val ox1 = xval oy1 = yval ox11 = x + outSpaceval oy11 = y + outSpaceoutRect1 = RectF(ox1.toFloat(), oy1.toFloat(), ox11.toFloat(), oy11.toFloat())//辅助矩形2val ox2 = x1 - outSpaceval oy2 = yval ox12 = x1val oy12 = y1outRect2 = RectF(ox2.toFloat(), oy2.toFloat(), ox12.toFloat(), oy12.toFloat())//闭合outPath = Path()outPath.addArc(outRect2, 270f, 180f)outPath.arcTo(outRect1, 90f, 180f)outPath.lineTo((x1 - outSpace / 2).toFloat(), y.toFloat())//画中间-中间和里面都是重复上面步骤 只是矩形长宽等距离缩小//画中间val cx = x + lineSpaceval cy = y + lineSpaceval cx1 = x1 - lineSpaceval cy1 = y1 - lineSpace//中间矩形实际高度val centerHeight = cy1 - cycenterRect = RectF(cx.toFloat(), cy.toFloat(), cx1.toFloat(), cy1.toFloat())//辅助矩形1val cx2 = cxval cy2 = cyval cx12 = cx + centerHeightval cy12 = cy + centerHeightcenterRect1 = RectF(cx2.toFloat(), cy2.toFloat(), cx12.toFloat(), cy12.toFloat())//辅助矩形2val cx3 = cx1 - centerHeightval cy3 = cyval cx13 = cx1val cy13 = cy1centerRect2 = RectF(cx3.toFloat(), cy3.toFloat(), cx13.toFloat(), cy13.toFloat())//闭合centerPath = Path()centerPath.addArc(centerRect2, 270f, 180f)centerPath.arcTo(centerRect1, 90f, 180f)centerPath.lineTo((cx1 - centerHeight / 2).toFloat(), cy.toFloat())startPoint.x = cx1 - centerHeight / 2startPoint.y = cy//画里面//画里面val ix = cx + lineSpaceval iy = cy + lineSpaceval ix1 = cx1 - lineSpaceval iy1 = cy1 - lineSpace//里面矩形实际高度val innerHeight = iy1 - iyinnerRect = RectF(ix.toFloat(), iy.toFloat(), ix1.toFloat(), iy1.toFloat())//辅助矩形1val ix2 = ixval iy2 = iyval ix12 = ix + innerHeightval iy12 = iy + innerHeightinnerRectL = RectF(ix2.toFloat(), iy2.toFloat(), ix12.toFloat(), iy12.toFloat())//辅助矩形2val ix3 = ix1 - innerHeightval iy3 = iyval ix13 = ix1val iy13 = iy1innerRectR = RectF(ix3.toFloat(), iy3.toFloat(), ix13.toFloat(), iy13.toFloat())//闭合innerPath = Path()innerPath.addArc(innerRectR, 270f, 180f)innerPath.arcTo(innerRectL, 90f, 180f)innerPath.lineTo((ix1 - innerHeight / 2).toFloat(), iy.toFloat())bgPath = centerPath//测量路径measure.setPath(centerPath, false)pathLength = measure.lengthicon = BitmapFactory.decodeResource(context.resources, R.drawable.icon_runaway)startBp = BitmapFactory.decodeResource(context.resources, R.drawable.start)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)//跑道背景canvas.drawPath(bgPath, bgPaint)//三条跑道线//三条跑道线canvas.drawPath(outPath, outPaint)canvas.drawPath(centerPath, centerPaint)canvas.drawPath(innerPath, innerPaint)//起点图片canvas.drawBitmap(startBp!!,(startPoint.x - (startBp.width * 2)).toFloat(),(startPoint.y - (startBp.height * 2)).toFloat(),iconPaint)//单人模式//画已运动距离路径,已运动的pathdstPath.reset()measure.setPath(centerPath, false)//拿到从0开始到距离为singleDistance 长度的 path片段measure.getSegment(0f, getPosition(singleDistance), dstPath, true)canvas.drawPath(dstPath, dstPaint)//拿到centerPath 上距离为 singleDistance 的终点坐标measure.getPosTan(getPosition(singleDistance), point, null)//画头像val halfWidth = icon.width / 2val halfHeight = icon.height / 2canvas.drawBitmap(icon, point[0] - halfWidth, point[1] - halfHeight, iconPaint)//画名字drawName(canvas, point, halfHeight, "微风轻起");//...}private fun drawName(canvas: Canvas, point: FloatArray, halfHeight: Int, name: String) {//画名字//头像顶部中心坐标val x = point[0]val y = point[1] - halfHeight//名字距离头像的间隔val spacing = dp2px(4).toFloat()textBounds.setEmpty()//计算给定字符串的边界,并将结果保存在给定的 textBounds 对象中。textPaint.getTextBounds(name, 0, name.length, textBounds)//画 文字坐标val ty: Floatval tx: Float = x - (textBounds.width() / 2)//确定基线-不了解的可以去了解下val metricsInt = textPaint.fontMetricsIntval dy = (metricsInt.bottom - metricsInt.top) / 2 - metricsInt.bottomty = y + dy - spacing//用于给文字加个背景 文字的中间水平线Yval ly = ty - (textBounds.height() / 2)canvas.drawLine(tx, ly, tx + textBounds.width(), ly, mTextBgPaint)canvas.drawText(name, tx, ty, textPaint)}/*** 预设:跑道 400米* 通过实际距离 经过和预设跑道距离 转换,得到 distance 在跑道上的具体距离,进而确定位置*/private fun getPosition(distance: Float): Float {return pathLength * distance / sumDis % pathLength}fun sp2px(spValue: Int): Int {val fontScale = resources.displayMetrics.scaledDensityreturn (spValue * fontScale + 0.5f).toInt()}fun dp2px(dp: Int): Int {val scale = resources.displayMetrics.densityreturn (dp * scale + 0.5f).toInt()}}

总结

以上是如意编程网为你收集整理的安卓自定义View-类似操场跑道的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得如意编程网网站内容还不错,欢迎将如意编程网推荐给好友。