虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > Android编程 > 提高Android应用手写流畅度(基础篇)

提高Android应用手写流畅度(基础篇)
类别:Android编程   作者:码皇   来源:互联网   点击:

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

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


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


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

<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;">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线程阻塞)导致看上去不连续。本文通过增加触摸点、减少刷新区域后,手写效率和效果得到明显改善,当然通过软件的进一步处理,手写效果还能得到进一步改善,这需要通过软件做插值处理(压力值也可以考虑通过插值算法算出来),具体在后面介绍。

相关热词搜索: 基础