android使用canvas绘制量角器(kotlin)

Android 仪表盘View Android仪表板 可以用手指改变运动轨迹 阅读详情

最近有一个项目需要一个量角的功能,简单说就是能够在屏幕上绘制两条线行成夹角并得到夹角角度,下面来看看具体实现流程:

1、在layout布局中放置一个ImageView(iv_canvas)

2、首先定义默认画笔paint,画笔的几个属性一看就知道是什么意思,这里就不在讲解了:

paint = Paint()
paint!!.strokeWidth = 16f
paint!!.color = Color.RED
paint!!.style = Paint.Style.STROKE

3、为ImageView设置触摸监听事件:

iv_canvas!!.setOnTouchListener(touch)

private val touch = object : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean {
         when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                 
            }
            MotionEvent.ACTION_MOVE -> {
                
            }
            MotionEvent.ACTION_UP -> {
                
            }
        return true
    }
}

4、绘制思路:

(1)当手指按下的时候,将按下那一刻的点记为A点坐标;当手指以直线移动的时候,将移动到的点记为B点坐标;当手指移动不在AB这条线上的时候,固定上一个坐标点为B,将移动的点记为C点;当手指抬起保持不变;

(2)有了三个点,开始绘制线,以B为顶点,绘制BA和BC两条线,根据ABC三点坐标形成的三角形计算出B点角度(根据数学三角函数进行计算),计算公式如下:

/**
  * 计算角度
  *
  * @param ax
  * @param ay
  * @param bx
  * @param by
  * @param cx
  * @param cy
  * @return
  */
private fun countAngel(ax: Float, ay: Float, bx: Float, by: Float, cx: Float, cy:Float):Int
{
    val cL2 = (ax - bx) * (ax - bx) + (ay - by) * (ay - by)
    val bL2 = (ax - cx) * (ax - cx) + (ay - cy) * (ay - cy)
    val aL2 = (bx - cx) * (bx - cx) + (by - cy) * (by - cy)
    val cosB = (aL2 + cL2 - bL2) /(2.0*Math.sqrt(cL2.toDouble())*Math.sqrt(aL2.toDouble()))
    return Math.round(Math.toDegrees(Math.acos(cosB))).toInt()
}

(3)接下来绘制弧线,由于Android的坐标系是从左上角开始的,向右为x轴,向下为y轴,而弧线则是以水平方向向右为起点,因此在绘制角度弧线的时候需要先进行偏移角度的计算,只有计算好偏移角度才能将角度的起点设置到AB或者CB线上,这样才能让弧线绘制到两条线之间,计算思路其实很简单,就是以B点为原点,向右为正半轴,顺时针距离最近的那条线(AB或者BC)与x轴正半轴形成的角度,就是我们需要的偏移角度,用到的数学知识有(计算直线的斜率和反三角函数),计算公式如下:

/**
     * 计算偏移角度
     *
     * @param ax
     * @param ay
     * @param bx
     * @param by
     * @param cx
     * @param cy
     * @return
     */
    private fun drawAngle(ax: Float, ay: Float, bx: Float, by: Float, cx: Float, cy: Float): Int {
        var startAngle: Int = 0
        val k1 = (ay - by) / (ax - bx)
        val k2 = (cy - by) / (cx - bx)
        Log.e(TAG, "drawAngle: $k1~~~~$k2")
        if (k1 < 0 && k2 < 0) {
            if (k1 > k2) {
                if (ay < by && cy < by) {
                    startAngle = countOffset(cx, cy, bx, by, 360)
                } else if (ay > by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy > by && by > ay) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                }
            } else {
                if (ay < by && cy < by) {
                    startAngle = countOffset(ax, ay, bx, by, 360)
                } else if (ay > by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (cy > by && by > ay) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                }
            }
        } else if (k1 > 0 && k2 > 0) {
            if (k1 > k2) {
                if (ay < by && cy < by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (ay < by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                } else if (cy > by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                }
            } else {
                if (ay < by && cy < by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (ay < by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy > by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                }
            }
        } else if (k1 > 0 && k2 < 0) {
            if (ay > by && by > cy) {
                startAngle = countOffset(cx, cy, bx, by, 1)
            } else if (ay < by && cy > by) {
                startAngle = countOffset(cx, cy, bx, by, 180)
            } else if (ay > by && cy > by) {
                startAngle = countOffset(ax, ay, bx, by, 1)
            } else if (by > ay && by > cy) {
                startAngle = countOffset(ax, ay, bx, by, 180)
            }
        } else if (k1 < 0 && k2 > 0) {
            if (ay > by && by > cy) {
                startAngle = countOffset(ax, ay, bx, by, 180)
            } else if (ay < by && cy > by) {
                startAngle = countOffset(ax, ay, bx, by, 1)
            } else if (ay > by && cy > by) {
                startAngle = countOffset(cx, cy, bx, by, 1)
            } else if (by > ay && by > cy) {
                startAngle = countOffset(cx, cy, bx, by, 180)
            }

        }
        return startAngle
    }


private fun countOffset(x: Float, y: Float, Bx: Float, By: Float, flag: Int): Int {
        val tanL = ((y - By) / (x - Bx)).toDouble()
        var angel = Math.round(Math.toDegrees(Math.atan(tanL))).toInt()
        Log.e(TAG, "drawAngle: $angel")
        if (flag == 180) {
            angel += 180
        } else if (flag == -180) {
            angel = 180 - angel
        } else if (flag == -360) {
            angel = 360 - angel
        } else if (flag == -1) {
            angel = -angel
        } else if (flag == 360) {
            angel += 360
        }
        Log.e(TAG, "drawAngle: $angel")
        return angel
    }

(4)然后在弧线上绘制角度文字即可,文字是在弧线正上方,所以也有偏移角度,这里的偏移角是以AB或者BC为起点进行偏移的,因此偏移角只要相对线偏移B角度除以2再减去差不多一半的文字角度即可。

(5)简单的绘制,到上一步就可以了,但是,当我们再次按下手指 并移动的时候,你有没有想过还需要什么效果?那就是当手指按下的时候,我们要获取到距离手指最近的点(A/B/C)并进行移动改变绘制的线和角度。

(6)最后,将绘制的放到ImageView中即可。以上的这些就是绘制的基本思路。

5、具体绘制:

(1)MotionEvent.ACTION_DOWN

if (isFirst) {
                        if (baseBitmap == null) {
                            baseBitmap = Bitmap.createBitmap(iv_canvas!!.width,
                                    iv_canvas!!.height, Bitmap.Config.ARGB_8888)
                            canvas = Canvas(baseBitmap!!)
                        }

                        startX = event.x
                        startY = event.y
                        Ax = event.x
                        Ay = event.y
                        Bx = event.x
                        By = event.y
                    } else {
                        val x = event.x
                        val y = event.y

                        val aL = (x - Ax) * (x - Ax) + (y - Ay) * (y - Ay)
                        val bL = (x - Bx) * (x - Bx) + (y - By) * (y - By)
                        val cL = (x - Cx) * (x - Cx) + (y - Cy) * (y - Cy)
                        var minL = if (aL > bL) bL else aL
                        minL = if (minL > cL) cL else minL
                        if (minL == cL) {
                            Cx = x
                            Cy = y
                            minFlag = 3
                        } else if (minL == bL) {
                            Bx = x
                            By = y
                            minFlag = 2
                        } else if (minL == aL) {
                            Ax = x
                            Ay = y
                            minFlag = 1
                        }
                    }

(2)MotionEvent.ACTION_MOVE

val stopX = event.x
                    val stopY = event.y
                    if (isFirst) {
                        if (stopY - startY != 0f && stopY - Ay != 0f) {
                            if (Math.round((stopX - startX) / (stopY - startY)) == Math.round((stopX - Ax) / (stopY - Ay))) {
                                Bx = stopX
                                By = stopY
                            } else {
                                Cx = stopX
                                Cy = stopY
                            }
                        }

                        startX = event.x
                        startY = event.y

                    } else {
                        if (minFlag == 1) {
                            Ax = stopX
                            Ay = stopY
                        } else if (minFlag == 2) {
                            Bx = stopX
                            By = stopY
                        } else if (minFlag == 3) {
                            Cx = stopX
                            Cy = stopY
                        }

                    }
                    // 绘制夹角线
                    canvas!!.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
                    paint!!.strokeWidth = 16f
                    paint!!.color = resources.getColor(R.color.theme)
                    paint!!.pathEffect = null
                    canvas!!.drawLine(Ax, Ay, Bx, By, paint!!)
                    canvas!!.drawLine(Bx, By, Cx, Cy, paint!!)

                    // 绘制夹角弧线
                    paint!!.strokeWidth = 8f
                    paint!!.pathEffect = DashPathEffect(floatArrayOf(8f, 8f), 0f)
                    mAngel = countAngel(Ax, Ay, Bx, By, Cx, Cy)
                    val offset = drawAngle(Ax, Ay, Bx, By, Cx, Cy).toFloat()
                    val offsetPx = 200f
                    val oval = RectF(0f, 0f, offsetPx, offsetPx)
                    oval.offset(Bx - offsetPx/2, By - offsetPx/2)
                    canvas!!.drawArc(oval, offset, mAngel.toFloat(), false, paint!!)

                    // 绘制弧线上角度文字
                    paint!!.textSize = 30f
                    paint!!.color = resources.getColor(R.color.bn_red_dark)
                    paint!!.pathEffect = DashPathEffect(floatArrayOf(8f, 0f), 0f)
                    paint!!.strokeWidth = 3f
                    val path = Path()
                    path.addArc(oval, offset + mAngel.toFloat() / 2-10, 50f)
                    path.addOval(oval, Path.Direction.CW);
                    canvas!!.drawTextOnPath(mAngel.toString() + "°", path, 0f, -50f, paint)

                    iv_canvas!!.setImageBitmap(baseBitmap)

(3)MotionEvent.ACTION_UP(可不写)

val x = event.x
                    val y = event.y
                    if (minFlag == 1) {
                        Ax = x
                        Ay = y
                    } else if (minFlag == 2) {
                        Bx = x
                        By = y
                    } else if (minFlag == 3) {
                        Cx = x
                        Cy = y
                    }
                    isFirst = false

以上这些就是整个绘制过程,希望对你我都有所帮助!

Android开发量角器,Paint绘制量角器,使用传感器获测量角度,使用相机预览。 立即下载

相关推荐

Android-实现在Kotlin中更方便使用canvas

实现在Kotlin中更方便使用canvas

android 自定义View:量角器的实现

一 天产品需求说需要实现一个量角器的功能:通过打开后摄,然后手动调节两条指针可以显示指针间的夹角。 一接到这个需求,脑海里面抛出几个问题:夹角怎么计算?还有那个两条指针滑动时跟随手指怎么变化怎么实现? 哎,不管了,先实现从简单到开始实现吧,先拆分任务: 1、实现相机的预览功能。 2、指针跟随手指的变化来移动...

johndon_forever的博客 1122

Android绘图之Paint的使用方法详解

主要给大家介绍了关于Android绘图之Paint使用的相关资料,文中通过示例代码介绍的非常详细,并给大家介绍了DrawText 基线确定的方法,需要的朋友可以参考借鉴,下面随着小编来一些学习学习吧。

HenCoder Android 开发进阶:自定义 View 1-3 文字的绘制

这期是 HenCoder 自定义绘制的第三期:文字的绘制。 之前的内容在这里:HenCoder Android 开发进阶 自定义 View 1-1 绘制基础HenCoder Android 开发进阶 自定义 View 1-2 Paint 详解 如果你没听说过 HenCoder,可以先看看这个:HenCoder:给高级 Android 工程师的进阶手册 简介 上期的 Paint 详解里已经说过,文...

weixin_33796205的博客 617

MobaXterm终端工具个人学习版安装包及常用配置备份

资源包含 MobaXterm 个人版的安装包以及日常运维常用的基础配置备份文件。适用于网络工程师及运维人员用于日常的服务器管理、SSH 连接及串口调试。 压缩包内附带了个人整理的软件使用技巧与配置说明(仅供个人学习参考)。请各位同行下载后注意,技术工具请尽量支持官方正版,本资源仅供个人测试与交流使用,切勿用于商业环境。

抖音直播小风车跳转企业微信全攻略:企业号申请+落地页配置详解

本文详细解析了抖音直播小风车跳转企业微信的全流程,包括企业号申请、小风车配置和落地页优化。通过实战指南,帮助企业高效实现私域流量沉淀,提升直播转化率,特别适合电商和营销从业者参考。

weixin_29289821的博客 613

autojs量角器-相机背景

以相机为背景, 绘制半圆, 手指触摸, 测量角度

snailuncle2的博客 1609

vTaskStartScheduler()详解

是启动 FreeRTOS 实时内核的“扳机”。在调用它之前,你只是创建了一些任务结构,但它们都处于“待命”状态,系统仍然在按传统的裸机前后台方式运行。调用这个函数后,FreeRTOS 内核会接管系统的控制权,开始根据优先级调度你创建的任务,多任务环境才真正开始。关键点:它没有参数。它的所有行为都由中的配置宏控制。它通常不会返回。一旦调用,除非没有任务可以运行(或者你显式地停止了调度器),否则程序流永远不会回到main()函数中调用它的下一行。是一个没有参数但行为由精密控制的函数。

Shylock_Mister的博客 1594

Android实战开发-Kotlin教程(布局篇 3.2)

上一篇我们对Android的布局方式有了一定的了解,本篇我们综合利前面学习的知识完整的搭建一个登录界面 创建完LoginActivity后,我们来到activity_login.xml布局文件中开始搭建布局 最外层布局我们采用RelativeLayout的相对布局,首先,是顶部放置对应App的logo,因为是图片显示,我们采用ImageView的组件显示,如下图所示 接下来,我们利用线性布局LinerLayout搭建一个用户和密码输入的区域 这里先创建一个垂直方向的线性布局,让用户名、密码以及提交按钮按

aaaym的博客 1400

window系统 dlib库安装

1. 安装visual studio 1.1. 安装vs2017或以上版本 1.2. 我们需要安装使用C++的桌面开发,且必须选上用于Cmake的Visual C++工具 1.3. 安装完成后,将D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin 添加到环境变量PATH中 2 下载cmake文件,并添加环境变量 2.1 在c

@bangbang的博客 815

一文吃透 ABAP RESTful Application Programming Model(RAP):从数据建模与行为,到业务服务暴露,再到服务消费

本文介绍了SAP ABAP统一编程模型RAP的核心架构与实践方法,通过构建完整的ZTRAVEL和ZBOOKING业务对象,演示了从数据建模、行为定义到服务暴露的全流程。文章详细说明了RAP三层架构(数据与行为层、服务提供层、服务消费层)的实现方式,包括CDS视图实体定义、行为定义(BDEF)中的事务能力声明、投影与服务绑定等关键步骤。特别强调了ABAP Cloud环境下的开发限制和最佳实践,如使用GUID和late numbering机制,以及如何通过EML语言操作业务对象。最后提供了完整的代码示例,展示了

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 522

Android画布Canvas绘图scale,Kotlin

文章浏览阅读9.6k次。文章浏览阅读1.8k次。/*Java代码 将Drawable转化为Bitmap */ Bitmap drawableToBitmap(Drawable drawable) { int width = drawable.getIntrinsicWidth();Android Material Design :LinearLayoutCompat添加分割线divider_linearlayout 分割线-优快云博客。

Zhang Phil 1036

【AI理论学习】手把手推导扩散模型:Diffusion Models(DDPM)

生成扩散模型DDPM如下图所示分为前向、逆向两个过程,它首先通过不断往原始清晰数据中添加噪声使其变成标准高斯噪声(前向过程),而后期望从标准高斯噪声中还原原始数据(逆向过程)。若能实现,那便可从已知的标准高斯分布中采样一个噪声数据,而后利用DDPM模型生成符合原始数据分布的新数据。 Diffusion模型包含两个过程:数学符号:y∝xy\propto xy∝x:y正比于x,即y随着x增大而线性增大。 条件概率:贝叶斯公式:p(A∣B)=p(B∣A)×p(A)p(B)p(A|B)=\frac{p(B|A)\

小哲的博客 1511

Android自定义控件开发入门与实战(12)Canvas

第九章、Cnavas与图层 获取Canvas对象的方法 方法一:重写onDraw()、dispatchDraw()函数 一般在自定义View时,我们都会重写onDraw()、dispatchDraw()函数。先来看下他们的定义: @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas)...

RikkaTheWorld 961

CSP-J2019普及组复赛T4:加工零件

CSP-J2020普及组复赛T1:优秀的拆分,广度优先遍历求最短路。

少儿编程乔老师 1854

目标检测旋转增强源码带方向

解法1: import cv2 import math import numpy as np import os def get_degree(p1,p0): aaa = math.degrees(math.atan2((p1[1] - p0[1]), (p1[0] - p0[0]))) if aaa < 0: aaa = 360 + aaa aaa = (aaa + 90) if aaa > 360: aa...

jacke121的专栏 2084

【Android+Kotlin】协程作业实践(协程,Bitmap,canvas,网络架构)

一:使用HttpURLConnection,Bitmap,Canvas,测试withcontent线程以及回调 1. 效果图 设置一张默认图片 图片加载后 2. 实现代码一,参考出处:参考了微信:小楠总 1. 协程导包 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.

qq_38304672的博客 473

倍福控制第三方伺服走CSV模式--以汇川伺服为例

在使用TwinCAT软件控制标准CiA402协议的EtherCAT伺服时使用TwinCAT软件NC自带的运动控制指令控制伺服,此时伺服驱动器一般工作在CSP/CSV/CST模式。最常见伺服驱动器工作在CSP模式,轨迹规划运行在TwinCAT软件,位置环速度环电流环运行在伺服驱动器中。该模式下速度波动较大,需要极度平稳的速度需要采用CSV模式。本文讲述的是伺服驱动器工作在CSV模式,此时轨迹规划和位置环均运行在TwinCAT软件,速度环电流环运行在伺服驱动器中。为了简化文字描述,本文采用图文结合的方式介绍伺服

weixin_41883890的博客 4848

Android自定义View实现方位刻度尺(类似于吃鸡手游)

Android自定义View实现方位刻度尺(类似于吃鸡手游) 先上效果图 gif可能看不清,我下面放几张图片 原理分析 首先,我们应该把看得到的内容从上至下分成三部分:最上面的文字、中间的竖线和最下面的三角形。 其次,每个竖线的间隔,根据屏幕宽度均分为10等份,每等分是5°,最小刻度单位1°(相当于1等份里又分成了5等份),屏幕上显示的区间间隔为50°。所以,每次...

求知的程序员 1964

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Android画布Canvas裁剪clipRect,Kotlin

Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。Android Bitmap保存成至手机图片文件,Kotlin_android bitmap保存图片-优快云博客。Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin-优快云博客。

Zhang Phil 1783

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Android Canvas方法总结

前言:当我们进行自定义控件的时候,为了实现更多更炫酷的效果,我们通常都需要在Canvas(画布) 上绘制各种东西。而谷歌也提供了很多的API方法,能让我们更方便更快捷的实现想要的效果。 Canvas public class Canvas extends Object java.lang.Object ...

weixin_30784501的博客 379

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

android 中角度计算

android 中角度计算问题记录: toDegrees(double angrad) 将以弧度测量的角度转换为以度为单位的近似等效角度。 toRadians(double angdeg) 将以度为单位的角度转换为以弧度测量的大致相等的角度。 角度: 当需要更准确的角度值时,如天文学中量度星体或地球的经度和纬度,除了可用小数表示,还可以把角度细分为角分和角秒:1度为60分(60′),1分为60秒(60″)。例如40.1875° = 40°11′15″。 角度和弧度 数学上是用弧度而非角度,因为360

dangyongping 3536

Android 刻度尺的另类实现方式.

最近UI同学出了一种新的交互, 通过调节刻度尺来调节温度的变化。 通常大家都会想到通过自定义View来实现这个刻度尺。今天我们通过RecyclerView来实现。 1 思考 通过观察, 整个刻度是一个横向排列的列表, 完全可以通过横向的RecyclerView来实现, 指示部分不动,可以固定在RecyclerView的中间部分。通过滑动RecyclerView,来改变刻度值。 看一下实现效果。 2 实现 首先写布局, 在屏幕中央实现一个横向的 RecyclerView的列表, 然后在RecyclerVi

xiaokangss的博客 1522

Android 简单快速实现 下弧形刻度尺(滑动事件)

【代码】Android 简单快速实现 下弧形刻度尺(滑动事件)

OneN的博客 1100

安卓Android自定义控件:200行代码实现一个简约时钟

一个简约而不无乐趣的时钟控件

DonMoses的专栏 5104

Android 自定义数字角标

需要显示购物车数量,网上看了一些实现方式,有的是通过把页面view控件作为子view添加的,但是会改变我已有的样式和操作,因此决定自己编写 实现显示角标的功能。 我这里的需求是使用FloatingActionButton 显示购物车的数量,但是考虑到FloatingActionButton一直停留在一个地方又会遮挡信息,因此是可拖动的。 网上看到有人推荐以下这个实现方式 : 红色角标实现https://github.com/qstumn/BadgeView 我把该项目移library的方式引入到我自己的项目

Lobby_Anny的博客 975
上一篇: kotlin 自定义监听器
下一篇: oracle 数据库导入
Xiazhenghang
博客等级 码龄10年 1粉丝 15原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值