前言

这是一篇翻译至squareup的文章,这是原文,之前有人在TIEYE上翻译过这篇文章,但现在链接已经失效,手写效率问题是一直是Android平台上一个比较棘手的问题,所以有必要将这篇文章带给Android开发者,这篇文章在ITEYE那篇译文的基础上有所改动,如果英语还可以,请尽量阅读原文。

正文

在上一篇文章中,我们讨论了Square如何在Android设备上把签名效果做的平滑。在最新发布的Android版Square Card Reader应用中,我们将签名效果更上一层楼,更平滑,更美观,响应更灵敏!

主要通过以下三个方面来改进用户体验效果:

使用改进的曲线算法;

笔划粗细变化;

以bitmap缓存提升响应能力。

曲弧之美:

当你在屏幕滑动手指进行签名时,Android将一序列的触屏事件传递给Square客户端,每个触屏事件都包含独立的 (x,y) 坐标。要创建出一个签名的图像,客户端需要重建这些采样触点之间的连接线段。计算连接一序列离散点之间连接线段的过程,称为样条插值。

最简单的样条插值策略是直线连接每一对触点。这也是之前版本的Square客户端采用的策略。

Splining0

可以看到,即使有足够多的触点去模拟签名的曲线,线性插值方法呈现的效果仍显得又硬又挫。仔细观察图中的签名曲线,可以发现连接线在触点处出现了硬角,原本应该是外圆弧状的地方呈现出难看的扁平状。

Splining1

问题原因在于,用户签名时手指并不是直愣愣地作点到点直线划动,更多情况下是曲线式的移动。但我们的SignatureView只能捕捉到签名过程中的采样点,再通过猜测采样点间连线来模拟用户签名的完整轨迹。显然,直线连接并不是很好的模拟。

这里较为合适的一个插值方法是曲线拟合。我们发现三次Bezier插值曲线是最理想的插值算法。我们能够利用Bezier控制点精确地确定曲线形状,更赞的是我们能够在网上轻松地找到很多高效的Bezier曲线绘制算法。

Bezier曲线绘制算法需要输入一组用于生成曲线的控制点,但我们目前得到的只有在曲线上的采样点本身,没有Bezier控制点。由此,我们的样条插值计算归结为,利用现有的采样触点,计算出一组用来作为Bezier绘制算法输入的控制点,画出目标曲线。

这里对平滑的三次方曲线绘制的相关数学知识不作详细讨论。有兴趣的朋友可以阅读Kirby Baker的UCLA计算机课程讲义。

完成了从线性插值到曲线插值,乍看差异很细微,但整体的圆滑效果提升却相当明显。

Splining2

笔划粗细变化:

如果你仔细研究下写在纸上的手写签名,不难发现笔划的粗细并不是一成不变的。笔划的粗细是随着笔的速度和用力程度而改变的。尽管Android提供了一个跟踪触屏力度的API,但其效果并没有达到我们用于签名所需的灵敏度与连贯性。还好,跟踪笔划速度是可以实现的,我们仅需要将每个触点的采集时间作tag标记,然后就可以计算点到点之间的速度了。

public class Point {

private final float x;

private final float y;

private final long timestamp;

// ...

public float velocityFrom(Point start) {

return distanceTo(start) / (this.time - start.time);

}

}

由于我们的绘制了签名的每个Bezier曲线,笔划的粗细依据可为每段曲线的起止点间的速度。

lastVelocity = initialVelocity;

lastWidth = intialStrokeWidth;

public void addPoint(Point newPoint) {

points.add(newPoint);

Point lastPoint = points.get(points.size() - 1);

Bezier bezier = new Bezier(lastPoint, newPoint);

float velocity = newPoint.velocityFrom(lastPoint);

// A simple lowpass filter to mitigate velocity aberrations.

velocity = VELOCITY_FILTER_WEIGHT * velocity

+ (1 - VELOCITY_FILTER_WEIGHT) * lastVelocity;

// The new width is a function of the velocity. Higher velocities

// correspond to thinner strokes.

float newWidth = strokeWidth(velocity);

// The Bezier's width starts out as last curve's final width, and

// gradually changes to the stroke width just calculated. The new

// width calculation is based on the velocity between the Bezier's

// start and end points.

addBezier(bezier, lastWidth, newWidth);

lastVelocity = velocity;

lastWidth = strokeWidth;

}

当我们动手实现的时候,却碰到了一个棘手的问题——Android的canvas API没有绘制曲线宽度可变的Bezier曲线的相关方法。这意味着我们必需以点成线,自己点画出目标曲线。

/** Draws a variable-width Bezier curve. */

public void draw(Canvas canvas, Paint paint, float startWidth, float endWidth) {

float originalWidth = paint.getStrokeWidth();

float widthDelta = endWidth - startWidth;

for (int i = 0; i < drawSteps; i++) {

// Calculate the Bezier (x, y) coordinate for this step.

float t = ((float) i) / drawSteps;

float tt = t * t;

float ttt = tt * t;

float u = 1 - t;

float uu = u * u;

float uuu = uu * u;

float x = uuu * startPoint.x;

x += 3 * uu * t * control1.x;

x += 3 * u * tt * control2.x;

x += ttt * endPoint.x;

float y = uuu * startPoint.y;

y += 3 * uu * t * control1.y;

y += 3 * u * tt * control2.y;

y += ttt * endPoint.y;

// Set the incremental stroke width and draw.

paint.setStrokeWidth(startWidth + ttt * widthDelta);

canvas.drawPoint(x, y, paint);

}

paint.setStrokeWidth(originalWidth);

}

可以看到,笔划粗细变化的签名,更加接近真实的手写效果。

Variable Stroke Width

响应能力:

影响一个签名过程愉悦程度的另外一个重要因素是对输入的响应能力。使用纸笔签名时,笔的移动与纸上笔划出现是没有任何延迟的。而在触摸屏设备上,出现响应延迟在所难免。我们要做的是尽可能地减少这种延迟感,缩短用户手指在屏幕上滑动与签名笔划出现之间的时间间隔。

一种简单渲染策略是将所有的Bezier曲线在我们signatureView的onDraw()方法中绘制。

@Override

protected void onDraw(Canvas canvas) {

for (Bezier curve : signature) {

curve.draw(canvas, paint, curve.startWidth(), curve.endWidth());

}

}

之前提到,我们绘制Bezierq曲线的方法是多次调用canvas.drawPoint(...)方法来以点成线。每个曲线重绘,对于笔划简单的签名还算可行,但对笔划较为复杂的签名则明显感觉到很慢。即使采用指定区域刷新的方法,绘制重叠线段仍然会严重拖慢签名响应。

解决方法是当签名每增加一个曲线时,将相应的Bezier曲线绘制到一个内存中的Bitmap中。之后只需要在onDraw()方法中画出该bitmap,而不需要在整个签名过程中对每条曲线重复运行Bezier曲线绘制算法。

Bitmap bitmap = null;

Canvas bitmapCanvas = null;

private void addBezier(Bezier curve, float startWidth, float endWidth) {

if (bitmap == null) {

bitmap = Bitmap.createBitmap(getWidth(), getHeight(),

Bitmap.Config.ARGB_8888);

bitmapCanvas = new Canvas(bitmap);

}

curve.draw(bitmapCanvas, paint, startWidth, endWidth);

}

@Override protected void onDraw(Canvas canvas) {

canvas.drawBitmap(bitmap, 0, 0, paint);

}

使用该方法能保证签名的绘制响应,不受签名复杂度的影响。

最终成品:

综上所述,我们采用了三次样条插值来使签名效果更平滑,基于笔划速度的笔划粗细可变效果使签名更真实,bitmap缓存使得绘制响应得到优化。最终的成果是用户能够得到一个愉悦的签名体验和一个漂亮的签名。

final

注:在github上可以下载到项目的源码:

android 手写 流畅,Android手写优化-更为平滑的签名效果实现相关推荐

  1. android 手写 流畅,提高Android应用手写流畅度(基础篇)

    在使用android类的手写应用时,整体上都有这样一个印象:android的手写不流畅.不自然,和苹果应用比起来相差太远.本文结合作者亲身经历,介绍一下有效提高手写流畅度的几种方法: 1.未做任何处理 ...

  2. 手写签名板 android,Android 简易签名板

    一个简单的练习,手写签名后,可以清空,保存,然后再相册进行查看 简易签名板 有5个知识点,需要注意: 在SignatureView的onTouchEvent()方法中,利用mPath.quadTo() ...

  3. Android游戏开发教程:手教你写跳跃类游戏

    Android游戏开发教程:手教你写跳跃类游戏 package jumpball.game; import android.app.Activity; import android.os.Bundle ...

  4. 红橙Darren视频笔记 手写ButterKnife(Android Studio4.2.2 gradle-6.7.1 )

    ButterKnife的github地址 https://github.com/JakeWharton/butterknife 1.ButterKnife的使用 第一步 在moudle的gradle配 ...

  5. android记事本 图文存储,android项目 之 记事本(15) ----- 保存手写及绘图

    之前,忘了写如何将手写和绘图保存,现在补上. 首先看如何保存绘图,先看效果图: 因为记事本的绘图功能主要用到了画布,而在构建画布时,指定了Bitmap,也就是说在画布上的所画的东西都被保存在了Bitm ...

  6. android 手写签批_Android手写签名效果

    任何画线的程序,都是先在界面上获取若干不连续的点,然后将这些点连成线. 一些常见的笔型比较好实现,比如说铅笔.钢笔等等,这类笔型的线条的宽度和线条的颜色是固定的,只需要将点连接成固定颜色和固定宽度的线 ...

  7. android 最新框架组合,android 官方mvp框架优化:lifecycle-mvp,像前端那样组合式写页面...

    目录 1 前言 虽然在标题上,自己很随意的起了这么一个名字.其实并不是说它起个英文名就牛逼了.说白了,它其实就是mvp的思想加了lifecycle-component,然后加入了分层的思想,最后用Ty ...

  8. 手写分页 个人感觉还能优化,甚至抽象出来,需要高手讲解

    本来就是想来学习下手写分页或者自己写下分页逻辑,就当是一次练习,数据用的是sql2005,数据量是432W. 首先先感谢国家.然后在感谢csdn和群里的朋友跟我一起讨论.当然拉我知道我的做法不是最好的 ...

  9. 逍遥android模拟器设置,逍遥安卓模拟器最佳设置电脑上玩手游流畅不卡多开更好用...

    电脑上玩腾讯手游使用哪个手游模拟器助手软件?对于目前大部分用户来说适合自己电脑情况的才是最好用的.大部分想在电脑上使用手游模拟器的用户要么是为了手游挂机方便,要么就是为了能够使用鼠标键盘大屏幕更好的操 ...

最新文章

  1. 深度学习在计算机视觉中的应用长篇综述
  2. php 链接多个mysql_PHP同时操作多个MySQL连接
  3. 体育与科技丨清华之友体育产业主题论坛成功举行
  4. 20135337朱荟潼 Linux第八周学习总结——进程的切换和系统的一般执行过程
  5. P4556,jzoj3397-[GDOI2014模拟]雨天的尾巴【树链剖分,线段树】
  6. 计算机设备的存放,计算机硬件储存设备与网络储存的发展现状
  7. python实训day5
  8. WebAPI基本封装
  9. classcastexception异常_让你为之颤抖的Java常见的异常exception
  10. 嵌入式 Linux 4.0,嵌入式多媒体中心 OpenELEC 4.0.4
  11. 车牌OCR识别SDK
  12. 基于MATLAB的幂级数求和与展开(Taylor和Fourier算法)
  13. 简单迭代法求解非线性方程组
  14. android支付宝支付
  15. 奔跑中的交银施罗德基金,崛起的新生代基金经理
  16. 实战一个项目后,谈谈 Rust 语言的优点和缺点
  17. 【JS】导出合并表格
  18. 以一元及二元函数为例,通过多项式的函数图像观察其拟合性能;以及对用多项式作目标函数进行机器学习时的一些理解。
  19. mysql查询某字段包含手机号
  20. C#网络爬虫(获取需要登录的网站数据)

热门文章

  1. 平安城市安防监控工程案例
  2. 电商类web原型制作分享——美丽说【附源文件】
  3. 配置GLFW和GLAD,使用OpenGL
  4. 探索Hive用户权限(二):HiveServer2安全访问Hive
  5. 苹果cms二开的麻豆影视主题模板源码
  6. ux设计工具_UX设计人员的5种视觉设计工具
  7. e人谷龙门阵之twitter.com
  8. Dates in Spoken English
  9. 因果推断4--Causal ML(个人笔记)
  10. ADODB.Connection 错误 '800a0e7a' 处理