Android

Android通过.nomedia文件禁止多媒体库扫描指定文件夹下的多媒体文件 Android应用内存泄露分析、改善经验总结 修改Eclipse导入项目的默认工程名 自定义Android Studio工程模板 使用Nexus Repository搭建属于自己公司的私有maven服务器 Android Studio编译过程中mergeDebugResources时报“png-cruncher_*”异常的解决方案 Eclipse转Android Studio的过程中有必要弄明白的一些问题 Android开发经验总结 Android Studio使用过程中遇到的一些问题及解决方案 Android各个Support Library介绍 调用AsyncTask的excute方法不能立即执行程序的原因分析及改善方案 提升进入界面的速度 使用软引用解决Handler内存泄露和显示Popupwindow、Dialog时提示"Unable to add Window-token is null"的问题 SharedPreferences在多进程中的使用及注意事项 Android性能测试工具列表 Android View双缓冲绘制时清除Bitmap上的内容的方法 解决JPinyin在APK被加密后不能正常使用的问题 Android APP内存优化之图片优化 Android EditText的使用及值得注意的地方 Android应用内多进程的使用及注意事项 Android设置应用内文字的默认颜色和大小 关于APK瘦身值得分享的一些经验 Android通过ClipDrawable实现图片裁剪功能 Android通过广播更新文件和文件夹到媒体库 每个Android开发者都应该了解的资源列表 selector的使用方法及注意事项 通过批处理批量clone代码 Android清除数据、清除缓存、一键清理的区别 Android将数据库保存到SD卡的实现 Android多分辨率适配经验总结 通过观察者模式监听媒体库的变化实现APP本地数据自动更新 Android ADB命令大全(通过ADB命令查看wifi密码、MAC地址、设备信息、操作文件、查看文件、日志信息、卸载、启动和安装APK等) Android通过ADB查看wifi密码 Android一个APK多个入口(多个桌面图标)的实现 使用Python脚本批量卸载第三方应用和清除log缓存 Android CheckList Android模仿打字机效果的自定义View实现 在Activity的onCreate方法中显示PopupWindow导致异常的原因分析及解决方案 Android手写优化-更为平滑的签名效果实现 Android手写优化-平滑的签名效果实现 不要在Android的Application对象中缓存数据! 大量Android面试题目来袭 一种不需要Google账号、不需要关联手机、不需要在手机上安装Google的服务直接能够下载Google Play上APK的方法 在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案 Android程序和数据分离的实现方案 按Home按键退出应用后重新启动该应用无法返回到最后打开页面的解决方案 Eclipse下Android项目不能生成R.java的解决方法汇总 android:descendantFocusability属性在ListView中的妙用 去掉SrollView、GrdiView、ListView、ViewPager等滑动到边缘的光晕效果 Android开发经验谈-Eclipse使用技巧 Android开发经验谈-很少有人会告诉你的Android开发基本常识 Android开发经验谈-Android工程目录介绍 在Android的string.xml中使用转义字符实现想要的显示效果 修改ViewPager调用setCurrentItem时,滑屏的速度 Android监听Home按键消息 Android手写开源项目和资料搜集 Android通过资源文件名获取资源ID Android中Bitmap、Drawable、bytes数组之间相互转换 想过但未实现的一些Idea 读写文件编码方式不一致导致文件乱码的解决方案 Android字符串格式化开源库phrase介绍 Android实现带箭头的自定义Progressbar Android模拟键盘输入功能的实现 与Android应用程序相关的各种文件存储路径介绍 Android开发者网址导航

标签

Android 65

Android通过.nomedia文件禁止多媒体库扫描指定文件夹下的多媒体文件 Android应用内存泄露分析、改善经验总结 修改Eclipse导入项目的默认工程名 自定义Android Studio工程模板 使用Nexus Repository搭建属于自己公司的私有maven服务器 Android Studio编译过程中mergeDebugResources时报“png-cruncher_*”异常的解决方案 Eclipse转Android Studio的过程中有必要弄明白的一些问题 Android开发经验总结 Android Studio使用过程中遇到的一些问题及解决方案 Android各个Support Library介绍 调用AsyncTask的excute方法不能立即执行程序的原因分析及改善方案 提升进入界面的速度 使用软引用解决Handler内存泄露和显示Popupwindow、Dialog时提示"Unable to add Window-token is null"的问题 SharedPreferences在多进程中的使用及注意事项 Android性能测试工具列表 Android View双缓冲绘制时清除Bitmap上的内容的方法 解决JPinyin在APK被加密后不能正常使用的问题 Android APP内存优化之图片优化 Android EditText的使用及值得注意的地方 Android应用内多进程的使用及注意事项 Android设置应用内文字的默认颜色和大小 关于APK瘦身值得分享的一些经验 Android通过ClipDrawable实现图片裁剪功能 Android通过广播更新文件和文件夹到媒体库 每个Android开发者都应该了解的资源列表 selector的使用方法及注意事项 通过批处理批量clone代码 Android清除数据、清除缓存、一键清理的区别 Android将数据库保存到SD卡的实现 Android多分辨率适配经验总结 通过观察者模式监听媒体库的变化实现APP本地数据自动更新 Android ADB命令大全(通过ADB命令查看wifi密码、MAC地址、设备信息、操作文件、查看文件、日志信息、卸载、启动和安装APK等) Android通过ADB查看wifi密码 Android一个APK多个入口(多个桌面图标)的实现 使用Python脚本批量卸载第三方应用和清除log缓存 Android CheckList Android模仿打字机效果的自定义View实现 在Activity的onCreate方法中显示PopupWindow导致异常的原因分析及解决方案 Android手写优化-更为平滑的签名效果实现 Android手写优化-平滑的签名效果实现 不要在Android的Application对象中缓存数据! 大量Android面试题目来袭 一种不需要Google账号、不需要关联手机、不需要在手机上安装Google的服务直接能够下载Google Play上APK的方法 在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案 Android程序和数据分离的实现方案 按Home按键退出应用后重新启动该应用无法返回到最后打开页面的解决方案 Eclipse下Android项目不能生成R.java的解决方法汇总 android:descendantFocusability属性在ListView中的妙用 去掉SrollView、GrdiView、ListView、ViewPager等滑动到边缘的光晕效果 Android开发经验谈-Eclipse使用技巧 Android开发经验谈-很少有人会告诉你的Android开发基本常识 Android开发经验谈-Android工程目录介绍 在Android的string.xml中使用转义字符实现想要的显示效果 修改ViewPager调用setCurrentItem时,滑屏的速度 Android监听Home按键消息 Android手写开源项目和资料搜集 Android通过资源文件名获取资源ID Android中Bitmap、Drawable、bytes数组之间相互转换 想过但未实现的一些Idea 读写文件编码方式不一致导致文件乱码的解决方案 Android字符串格式化开源库phrase介绍 Android实现带箭头的自定义Progressbar Android模拟键盘输入功能的实现 与Android应用程序相关的各种文件存储路径介绍 Android开发者网址导航

Android手写优化-更为平滑的签名效果实现

2014年12月29日

前言

  这是一篇翻译至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-signaturepad