android画板笔锋实现
前言
在安卓绘图中,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画板笔锋实现的更多相关文章
- Android 实现图片画画板
本文主要讲述了Android 实现图片画画板 设计项目布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk ...
- android 图片画画板
canvas.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: ...
- Android(java)学习笔记238:多媒体之图片画画板案例
1.首先我们编写布局文件activity_main.xml如下: <RelativeLayout xmlns:android="http://schemas.android.com/a ...
- Android利用canvas画画板
首先新建一个项目工程,建立文件,如下图所示
- Android绘画板(普通绘画模式和缩放平移绘画模式)
ScaleSketchPadDemo 项目地址: demo apk体验下载 demo2 apk体验下载 用法: 进入项目根目录:https://github.com/ShaunSheep/ScaleS ...
- Android 开源可缩放平移的绘画板
ScaleSketchPadDemo 此项目包含两个模块 app1 为普通绘画板 app2 为可所发的绘画板 方便各位Android 开发者理解和使用 用法: 进入项目根目录:https://gith ...
- Android简单开发的画画板
Android开发画画板要考虑得几个问题如下: 1 屏幕画板.画笔如何绘制问题 2 用户手指触摸屏幕画板监听事件,以及对应的几种状态处理问题 3 保存图片到SD卡,以及在系统相册打开时自动加载刚才的 ...
- Android简易实战教程--第二十四话《画画板》
今天完成一个画画板. 首先来个布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...
- 【Android】21.1 画板资源
分类:C#.Android.VS2015: 创建日期:2016-03-19 一.简介 画板资源(Drawable Resources)是用XML描述/Resources/drawable中的2D图形文 ...
随机推荐
- odoo开发笔记:前端显示强制换行
未调整之前:客户信息显示不全 调整后实现效果: 补充CSS知识: 一.强制换行 word-break: break-all; 只对英文起作用,以字母作为换行依据. word-wrap: break-w ...
- Python多线程、多进程实现
劝君莫惜金缕衣,劝君惜取少年时.花开堪折直须折,莫待无花空折枝. 内容摘要: paramiko模块 进程.与线程区别 python GIL全局解释器锁 多线程 语法 join 线程锁 ...
- Linux 服务器部署 PgAdmin 4 Server
PostgreSQL 使用率越来越高,但是好用的图形化软件,诸如 Navicat.DataGrip.HeidiSQL 都是客户端软件,对于访问控制来说,比起 MySQL 的 phpMyAdmin 更加 ...
- Mac下安装Eclipse和Tomcat等
Mac下做Java开发还是很方便的,不用像.NET开发一样在Parallel Desktop里面安装Windows虚拟机,Mac下面默认已经安装了JDK. 当然,你如果要安装JDK7,请先阅读:htt ...
- Windows下的Jdk 1.7*安装并配置(图文详解)
不多说,直接上干货! 很多人很少去想,为什么在windows下,安装完Jdk的安装包之后,还需要去配置环境变量,只是知道要这么去做,没有想过为什么要去这么做? 答:由于java是平台无关的 ,安装jd ...
- Windows下的Jdk 1.8*安装并配置(图文详解)
不多说,直接上干货! 简单说下,jdk1.8*的下载,见http://www.cnblogs.com/zlslch/p/5658383.html 双击jdk-8u60-windows-x64.exe运 ...
- 全网最详细的Hadoop HA集群启动后,两个namenode都是standby的解决办法(图文详解)
不多说,直接上干货! 解决办法 因为,如下,我的Hadoop HA集群. 1.首先在hdfs-site.xml中添加下面的参数,该参数的值默认为false: <property> < ...
- 高可用的MongoDB集群-实战篇
1.概述 最近有同学和网友私信我,问我MongoDB方面的问题:这里我整理一篇博客来赘述下MongoDB供大家学习参考,博客的目录内容如下: 基本操作 CRUD MapReduce 本篇文章是基于Mo ...
- 简述serializable和transient关键字作用
transient的作用及使用方法,官方解释为: Variables may be marked transient to indicate that they are not part of the ...
- Python中bisect的使用
在<Think Python>中第十章的练习中,涉及到了分半查找的bisect模块.为此,在网上查阅了Python中bisect模块的相关内容.有几个链接相对权威和明白: 1> ht ...