提高Android应用手写流畅度(基础篇)

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

1、未做任何处理的手写效果:

这是一个自定义的view,通过在onTouchEvent时间中捕获系统回调的触摸点信息,然后再onDraw方法里面刷新,可以明显地感觉到线条很生硬,并且在手写的过程中跟随感很差,反应迟钝,具体代码如下:

package com.mingy.paint.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class PaintOrignalView extends View {
	public PaintOrignalView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView();
	}

	public PaintOrignalView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView();
	}

	public PaintOrignalView(Context context) {
		super(context);
		initPaintView();
	}

	public void clear( ){
		if( null != mPath ){
			mPath.reset( );
			invalidate( );
		}
	}

	private void initPaintView() {
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.BLACK);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeWidth(5f);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawPath(mPath, mPaint);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float eventX = event.getX();
		float eventY = event.getY();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			mPath.moveTo(eventX, eventY);
			invalidate();
		}
			return true;
		case MotionEvent.ACTION_MOVE: {
			mPath.lineTo(eventX, eventY);
			invalidate();
		}
			break;
		case MotionEvent.ACTION_UP: {
			mPath.lineTo(eventX, eventY);
			invalidate();
		}
			break;
		default: {

		}
			return false;
		}

		return true;
	}

	private Paint mPaint = new Paint();
	private Path mPath = new Path();
}

通过分析,发现效率低下的原因是:

(1)底层回调给onTouchEvent方法中的点太少(单位时间内点信息少导致跟随感差,快速手写时点之间距离过长);

(2)捕获点信息后通知View刷新时,刷新不及时(刷新区域太大);

结合查阅的MotionEvent和View的api文档,发现可以从如下两个方向着手来提高手写体验:

2、增加触摸点个数:

显然我们无法改善系统回调onTouchEvent的次数,所以只能通过插值的方式来增加触摸点个数,但遗憾的时通过插值计算出来的点是没有压力值的,不方便做笔锋效果,通过查阅MotionEvent的api文档发现,Android对触屏事件进行批量处理。传递给onTouchEvent()的每一个MotionEvent都包含上至前一个onTouchEvent()调用之间捕获的若干个坐标点。如果将这些点都加入到绘制中,可使手写效果更加平滑。Android
Developers对MotionEvent的介绍如下:

将这些点取出来,跟随感有明显改善,并且随着单位时间内点数的增多,快速手写时点之间距离减小,看上去更为平滑:

修改后的代码如下:

package com.mingy.paint.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class PaintMorePointsView extends View {
	public PaintMorePointsView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaintView();
	}

	public PaintMorePointsView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView();
	}

	public PaintMorePointsView(Context context) {
		super(context);
		initPaintView();
	}

	public void clear( ){
		if( null != mPath ){
			mPath.reset( );
			invalidate( );
		}
	}

	private void initPaintView() {
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.BLACK);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeWidth(5f);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawPath(mPath, mPaint);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float eventX = event.getX();
		float eventY = event.getY();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			mPath.moveTo(eventX, eventY);
			invalidate();
		}
			return true;
		case MotionEvent.ACTION_MOVE: {
			int historySize = event.getHistorySize();
	        for (int i = 0; i < historySize; i++) {
	          float historicalX = event.getHistoricalX(i);
	          float historicalY = event.getHistoricalY(i);
	          mPath.lineTo(historicalX, historicalY);
	        }

			mPath.lineTo(eventX, eventY);
			invalidate();
		}
			break;
		case MotionEvent.ACTION_UP: {
			int historySize = event.getHistorySize();
	        for (int i = 0; i < historySize; i++) {
	          float historicalX = event.getHistoricalX(i);
	          float historicalY = event.getHistoricalY(i);
	          mPath.lineTo(historicalX, historicalY);
	        }

			mPath.lineTo(eventX, eventY);
			invalidate();
		}
			break;
		default: {

		}
			return false;
		}

		return true;
	}

	private Paint mPaint = new Paint();
	private Path mPath = new Path();
}

3、减少每次刷新的区域:

通过2改善了手写流畅度和平滑度,但是还可以做进一步改善,通过减小每次刷新的区域(使用invalidate(Rect rect)方法),可以提高刷新的效率,上面的代码都是对整个view进行刷新,当view过大(比如填充整个屏幕)时,手写过程中还是能够感觉到迟钝的现象,改善后的效果如下:

代码如下:

package com.mingy.paint.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class PaintInvalidateRectView extends View {
	public PaintInvalidateRectView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		initPaintView();
	}

	public PaintInvalidateRectView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaintView();
	}

	public PaintInvalidateRectView(Context context) {
		super(context);
		initPaintView();
	}

	public void clear() {
		if (null != mPath) {
			mPath.reset();
			invalidate();
		}
	}

	private void initPaintView() {
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.BLACK);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeWidth(5f);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawPath(mPath, mPaint);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float eventX = event.getX();
		float eventY = event.getY();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			mPath.moveTo(eventX, eventY);
			mLastTouchX = eventX;
			mLastTouchY = eventY;
		}
			return true;

		case MotionEvent.ACTION_MOVE:
		case MotionEvent.ACTION_UP: {
			resetDirtyRect(eventX, eventY);
			int historySize = event.getHistorySize();
			for (int i = 0; i < historySize; i++) {
				float historicalX = event.getHistoricalX(i);
				float historicalY = event.getHistoricalY(i);
				getDirtyRect(historicalX, historicalY);
				mPath.lineTo(historicalX, historicalY);
			}

			mPath.lineTo(eventX, eventY);

			invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
					(int) (mDirtyRect.top - HALF_STROKE_WIDTH),
					(int) (mDirtyRect.right + HALF_STROKE_WIDTH),
					(int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));

			mLastTouchX = eventX;
			mLastTouchY = eventY;
		}
			break;

		default:
			return false;
		}

		return true;
	}

	private void getDirtyRect(float historicalX, float historicalY) {
		if (historicalX < mDirtyRect.left) {
			mDirtyRect.left = historicalX;
		} else if (historicalX > mDirtyRect.right) {
			mDirtyRect.right = historicalX;
		}
		if (historicalY < mDirtyRect.top) {
			mDirtyRect.top = historicalY;
		} else if (historicalY > mDirtyRect.bottom) {
			mDirtyRect.bottom = historicalY;
		}
	}

	private void resetDirtyRect(float eventX, float eventY) {
		mDirtyRect.left = Math.min(mLastTouchX, eventX);
		mDirtyRect.right = Math.max(mLastTouchX, eventX);
		mDirtyRect.top = Math.min(mLastTouchY, eventY);
		mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
	}

	private static final float STROKE_WIDTH = 5f;
	private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
	private float mLastTouchX = 0;
	private float mLastTouchY = 0;

	private final RectF mDirtyRect = new RectF();
	private Paint mPaint = new Paint();
	private Path mPath = new Path();
}

后记:

由于Android的消息传递机制问题,驱动层传递给上层的点由于延时会丢失一部分,导致上层应用获取的点相对于系统给的点大大减少,虽然人眼在1秒钟内只要看到超过24帧就不能看出卡顿的现象,但由于刷新机制和由于其它原因(比如:UI线程阻塞)导致看上去不连续。本文通过增加触摸点、减少刷新区域后,手写效率和效果得到明显改善,当然通过软件的进一步处理,手写效果还能得到进一步改善,这需要通过软件做插值处理(压力值也可以考虑通过插值算法算出来),具体在后面介绍。

时间: 2024-12-17 18:29:59

提高Android应用手写流畅度(基础篇)的相关文章

百度面试两板斧:手写算法问基础

阅读本文大概需要 4 分钟. 作者:黄小斜 17年7月份,我参加了百度的实习生面试,随后在百度开始了半年的实习生活,18年7月份,我参加了百度的校招提前批面试,由于可以同时参加百度多个部门的提前批面试,结果我前前后后面试了10多次,也算是一段比较奇葩的经历了. 当然,实习生面试是这里面最简单的一次了,三轮面试,前两轮都是在问基础,问的也不深入,第三轮面试则直接谈人生谈理想.其实百度的日常实习生面试难度确实比校招要来的容易,因为百度一年四季都在招实习生,反观阿里和腾讯,只有在春招期间招收实习生.

[Android 性能优化系列]内存之基础篇--Android怎样管理内存

大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地址:http://developer.android.com/training/articles/memory.html 在接下来的一段时间里,我会每天翻译一部分关于性能提升的Android官方文档给大家 以下是本次的正文: ################ 随机訪问存储器(Ram) 无论在哪种软件

[Android 性能优化系列]内存之基础篇--Android如何管理内存

大家如果喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地址:http://developer.android.com/training/articles/memory.html 在接下来的一段时间里,我会每天翻译一部分关于性能提升的Android官方文档给大家 下面是本次的正文: ################ 随机访问存储器(Ram) 不管在哪种软件

自己练习极速赛车平台开发写的--C#基础篇十小练习

[csharp] view plain copy极速赛车平台开发论坛:haozbbs.com Q1446595067 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace P03 { class Program { static void Main(string[] args) { Test03_01

Android手写开源项目和资料搜集

引言 Android的手写效率一直是件头疼的事情,比如手写效率.笔锋效果.手掌抑制等等,本文搜集了关于手写的开源项目和一些相关的文章资料. 开源项目 1 android-signaturepad 项目地址:android-signaturepad 项目介绍:这是一款银行手写签名的应用,通过event的getHistory方法获取存储在MotionEvent中的历史点,大大提高了手写的流畅度,通过算法实现了笔锋效果. 2  Markers 项目地址:Markers 项目介绍:这是一款带有笔锋效果的

提升c++builder 代码输入流畅度的配置

提高c++builder 代码输入流畅度 1.输入指针的函数名后,识别函数参数移动光标到括弧内,此功能太慢,有明显延迟,建议关闭.关闭以后,输入函数名不会自动添加(),需要自己手动输入括弧了,不过速度快了啊.要看函数参数的定义快捷键Ctrl+Shift+Space this->Focused() this->FocusControl(); 去掉下面2个勾,此功能是IDE自带. Auto Parenthesis Code Parameters 2.输入任意两个字母,弹出相关的变量.函数等方便选择

React深入 - 手写redux api

简介: 手写实现redux基础api createStore( )和store相关方法 api回顾: createStore(reducer, [preloadedState], enhancer) 创建一个 Redux store 来以存放应用中所有的 state reducer (Function): 接收两个参数,当前的 state 树/要处理的 action,返回新的 state 树 preloadedState: 初始时的 state enhancer (Function): stor

[Android 性能优化系列]内存之终极篇--降低你的内存消耗

大家如果喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地址:http://developer.android.com/training/articles/memory.html 在接下来的一段时间里,我会每天翻译一部分关于性能提升的Android官方文档给大家 建议大家在看本文之前先去我的博客看看 [Android 性能优化系列]内存之基础篇--Andr

&lt;转&gt;Android App性能评测分析-流畅度篇

1.前言 在手机App竞争越来越激烈的今天,Android App的各项性能特别是流畅度不如IOS,安卓基于java虚拟机运行,触控响应的延迟和卡顿比IOS系统严重得多.一些下拉上滑.双指缩放快速打字等操作,安卓的流畅度都表现比较糟糕,但是,对于App使用过程是否流畅,一直没有一个可靠的指标将用户的客观感受和数据一一对应.虽然之前有FPS(每秒帧数)作为游戏或视频类App的性能指标,但对于那些界面更新不多的App来说,仍不是一个合适的衡量数据.以下会根据实际app性能测试案例,展开进行app性能