前言

在安卓绘图中,path是一个很常用的类,使用它可以实现基本的画线功能,但是自己用path画出来的同一条线段大小是不会改变的。如果做书写类型的软件,当然想要实现更好的逼真的书写效果,在实际书写过程中,我们的笔迹通常是带有笔锋的。因此,这篇文章主要讲解一下具体的实现思路,具体代码就不放上来了,有兴趣的可以私密我交流一下。

思路

要实现笔锋,首先要获取一个变化的值,这个值能决定画线的大小。在安卓手机中,能充当这个值的目前我能想到的有压感和滑动速度,而压感在某些安卓设备是无法获取的,因此我采用滑动速度作为这个变化值。从实际测试来看,这个值是完全可以做到仿真笔锋的。速度可以通过时间和距离计算出来,然后可以根据这个速度,计算宽度的大小,速度越快,两点间的线条宽度越小,反之则越大。线条采用贝塞尔曲线来画,可以得到更为圆滑的曲线,贝塞尔曲线可以手动画点成线,也可以直接path.quadTo(),但是path.quadTo()无法控制同一条贝塞尔曲线的宽度,因此理论上无法实现大小变化。所以可以采用画点成线的方案,控制每一点的大小,优点是笔锋比较完美,缺点是运算量较大,在一些性能不是很好的安卓设备中表现特别差。另一个方案是先用path.quadTo()画一条贝塞尔曲线,然后再利用pathMeasure.getSegment()分割path,通过设定参数,把这段贝塞尔曲线的path分割为固定长度的新path,然后给新path添加不同宽度的画笔即可。这样做的优点是效率大大提高,可以适应性能低的设备,缺点是表现效果没有画点好,如果长度没有适配好,线条会出现段落感。

画点成线笔锋实现

相关变量

lastVelocity 初始速度或上一条贝塞尔曲线速度
originalWidth 初始宽度
lastWidth 上一条贝塞尔曲线宽度
time 点的触摸时间
minWidth 最小宽度
VELOCITY_WEIGHT 权重
DST_WIDTH 宽度变化范围量
velocity 当前速度

初始速度

笔迹初始速度要根据情况的不同而设置不同的值,在首次画线的时候,初始速度为0。但是如果是笔锋线段擦除后重绘,则擦除分割出来的线段,每一段的初始速度是不一样的,第一段初始速度仍然是0,而分割出的其他笔迹线段的初始速度是上一部分线段的速度。注:上一部分线段为已被擦除的线段,速度被记录为lastVelocity

初始宽度

初始宽度就是白板设置的笔迹宽度originalWidth,用于笔锋擦除。因为笔锋擦除后分割出来的笔迹的开始宽度需要根据未分割的整段笔锋笔迹来计算,因此即使笔迹的前半部分已经被擦除,有了初始宽度和初始速度,依然可以计算出线段开始宽度,这样可以保证在擦除笔迹部分线段后其他线段还能保持不变。

上一段线段速度

二阶贝塞尔曲线是两个点加一个控制点形成的曲线,因此三个点形成一条贝塞尔曲线,而一条笔迹则由N条贝塞尔曲线形成,橡皮擦擦除实际上是擦除某一条或者N条贝塞尔曲线。因此如果某一条贝塞尔曲线前面的贝塞尔曲线被擦除了,那么它就作为新笔迹的初始线段,它需要一个初始速度,也就是前面所说的初始速度lastVelocity,实际上就是上一条贝塞尔曲线的速度。

上一段线段宽度

同上一段线段速度,也是保存的上一条贝塞尔曲线的最后宽度lastWidth。

每个点的经过时间

在onTouch触摸获取点的时候,可以把每个点的触摸时间time记录下来,则用两个点就可以计算出时间和距离,从而得出两点间的速度。

最小宽度

在会议平板上,1个像素的线条在视觉上表现不好,因此可以设置一个最小宽度,即使滑动速度非常快,线条的宽度大小也限定在这个最小宽度之上,目前设置minWidth为2。

权重

计算速度的时候,会根据上一段的速度得出一个比较合理的当前速度。如下:

velocity = (VELOCITY_WEIGHT * velocity + (1-VELOCITY_WEIGHT) * lastVelocity);

代码中的VELOCITY_WEIGHT则为当前采用的权重值,值越大宽度变化越明显,范围在0~1之间。

宽度变化范围

在现实画笔锋的情况中,不存在在很短的距离中出现大小变化巨大的线段,因此前一条的贝塞尔曲线大小不能和后一段贝塞尔曲线大小相差太大,需要设定一个范围值,超出范围值则强制限定在范围值内。如下:

newWidth = Math.min(newWidth, lastWidth + DST_WIDTH);
newWidth = Math.max(newWidth, lastWidth -DST_WIDTH);

代码中lastWidh+DST_WIDTH和lastWidth-DST_WIDTH就是宽度变化范围大小,DST_WIDTH就是相对上一条线段的宽度可变化量。

画贝塞尔曲线

由于两点之间范围不是很大,因此采用二阶贝塞尔曲线和三阶贝塞尔曲线在观感上差别不大,而二阶贝塞尔曲线画起来要比三阶贝塞尔曲线效率快很多,因此采用二阶贝塞尔曲线画线。二阶贝塞尔曲线的公式为:

B(t)=(1-t)²P0+2t(1-t)p1+t²p2, t∈[0,1]

其中p0代表坐标点第一点,p1代表控制点,p2代表坐标点第二点,t=i/steps,steps是总共需要补充的点的数量,i是当前补充到的第i个点的索引。根据这个公式就可以计算出当前需要补充的点和点的大小:

dWidth = endWidth - startWidth;
width = startWidth + tt * dWidth

截取path笔锋实现

第二方案在第一方案的基础上,去掉了手动计算贝塞尔曲线和画每一个点的大小的绘图方案。并且不再在笔迹对象中保存初始速度和原始宽度,而是保存当前线段大小。一条笔迹是由很多部分的贝塞尔曲线组成,那么只需要在第一次画笔迹抬手后,计算每一小段贝塞尔曲线的宽度,然后从组成贝塞尔曲线的三个点中,取第一个点的时间值,变更为当前贝塞尔曲线宽度值,则重绘的时候直接取这个宽度值作为新的宽度即可省略掉重复计算宽度(重绘的时候不需要再次计算,因此不需要原来的time值)。

相关变量

lastVelocity 初始速度或上一条贝塞尔曲线速度
originalWidth 初始宽度
lastWidth 上一条贝塞尔曲线宽度
time 点的触摸时间或当前贝塞尔曲线的宽度值
minWidth 最小宽度
VELOCITY_WEIGHT 权重

画贝塞尔曲线

画贝塞尔曲线直接使用path.quadTo()函数即可,不过需要使用PathMeasure的getLength()函数获取当前贝塞尔曲线长度,如果小于一个设定长度,则直接画在画布上,然后继续下一条贝塞尔曲线。如果大于设定长度,则使用PathMeasure的getSegment()函数截取设定长度的path,然后重新设定paint大小,画这条path,重复,直到截取完毕,继续下一条贝塞尔曲线。循环画path代码如下:

for (float i = 0, j = 1; i < pathLength; i += dl, j++) {
pathMeasure.getSegment(i, i + dl, newPath, true);
paint.setStrokeWidth(startWidth - dw * j);
canvas.drawPath(newPath, paint);
newPath.reset();
}

android画板笔锋实现的更多相关文章

  1. Android 实现图片画画板

    本文主要讲述了Android 实现图片画画板 设计项目布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk ...

  2. android 图片画画板

    canvas.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: ...

  3. Android(java)学习笔记238:多媒体之图片画画板案例

    1.首先我们编写布局文件activity_main.xml如下: <RelativeLayout xmlns:android="http://schemas.android.com/a ...

  4. Android利用canvas画画板

    首先新建一个项目工程,建立文件,如下图所示

  5. Android绘画板(普通绘画模式和缩放平移绘画模式)

    ScaleSketchPadDemo 项目地址: demo apk体验下载 demo2 apk体验下载 用法: 进入项目根目录:https://github.com/ShaunSheep/ScaleS ...

  6. Android 开源可缩放平移的绘画板

    ScaleSketchPadDemo 此项目包含两个模块 app1 为普通绘画板 app2 为可所发的绘画板 方便各位Android 开发者理解和使用 用法: 进入项目根目录:https://gith ...

  7. Android简单开发的画画板

    Android开发画画板要考虑得几个问题如下: 1 屏幕画板.画笔如何绘制问题 2 用户手指触摸屏幕画板监听事件,以及对应的几种状态处理问题 3  保存图片到SD卡,以及在系统相册打开时自动加载刚才的 ...

  8. Android简易实战教程--第二十四话《画画板》

    今天完成一个画画板. 首先来个布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...

  9. 【Android】21.1 画板资源

    分类:C#.Android.VS2015: 创建日期:2016-03-19 一.简介 画板资源(Drawable Resources)是用XML描述/Resources/drawable中的2D图形文 ...

随机推荐

  1. opencv2函数学习之blur,GaussianBlur,medianBlur和bilateralFilter:实现图像平滑处理

    在opencv2中,可能使用blur对图像进行平滑处理,这种方法就是最简单的求平均数. 平滑 也称 模糊, 是一项简单且使用频率很高的图像处理方法. 平滑处理的用途有很多, 但是在很多地方我们仅仅关注 ...

  2. HuLu机器学习问题与解答系列(1-8)

    声明:本系列文章转载自微信公众号HULU,本人只是搬运工,仅供学习,如有不妥,后续告知删除. 嗨,欢迎回来,希望你能保持定期回顾的好习惯噢!下面是Hulu机器学习问题与解答系列的前8篇内容,点击主题名 ...

  3. java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.String错误的解决方法

    mmobjectid是在Oracle数据库中对应的是Number类型的,在JavaBean中定义的是Long类型的. List<BigDecimal> mmobjidAllFromMars ...

  4. CentOS安装Nginx 以及日志管理

    环境:CentOS-6.4 Nginx版本:nginx-1.6.2.tar Linux连接工具:XShell VMWare虚拟机上准备两台CentOS: 两台机器做同样操作(后边做负载均衡.高可用的时 ...

  5. tomcat如何正确的开启远程调试功能

    在日常开发中,有时需要对远程服务器上的应用进行远程调试,对于tomcat,要进行远程调试其实很简单,只需要在启动tomcat时开启jpda服务即可. 什么是JPDA呢? JPDA(JavaPlatfo ...

  6. 常见hash原理

    原文出处:http://www.itmian4.com/forum.php?mod=viewthread&tid=4736&fromuid=1 散列表,它是基于快速存取的角度设计的,也 ...

  7. KMP算法中next数组的理解与算法的实现(java语言)

    KMP 算法我们有写好的函数帮我们计算 Next 数组的值和 Nextval 数组的值,但是如果是考试,那就只能自己来手算这两个数组了,这里分享一下我的计算方法吧. 计算前缀 Next[i] 的值: ...

  8. 第一次项目上Linux服务器(二:——安装jdk)

    本人采用的是rpm安装jdk1.8 1.下载jdk 去jdk下载页面找到要下载的jdk 本人下载的是jdk-8u161-linux-x64.rpm,百度云资源链接:链接:https://pan.bai ...

  9. tcp/ip通信中udp头部结构udphdrp->check校验计算

    通过raw socket修改通信数据后,可通过函数 set_udp_checksum1 重新校验计算iph->check值 在http://www.cnblogs.com/dpf-10/p/78 ...

  10. ASP.NET Repeater控件实现简单分页

    早上,有看MSDN,看到了 PagedDataSource 类 http://msdn.microsoft.com/zh-cn/library/system.web.ui.webcontrols.pa ...