提高Android應用手寫流暢度(基礎篇)

來源:互聯網
上載者:User

提高Android應用手寫流暢度(基礎篇)

在使用android類的手寫應用時,整體上都有這樣一個印象:android的手寫不流暢、不自然,和蘋果應用比起來相差太遠。本文結合作者親身經曆,介紹一下有效提高手寫流暢度的幾種方法:


1、未做任何處理的手寫效果:


這是一個自訂的view,通過在onTouchEvent時間中捕獲系統回調的觸摸點資訊,然後再onDraw方法裡面重新整理,可以明顯地感覺到線條很生硬,並且在手寫的過程中跟隨感很差,反應遲鈍,具體代碼如下:

<喎?http://www.bkjia.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);}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawPath(mPath, mPaint);}@Overridepublic 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);}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawPath(mPath, mPaint);}@Overridepublic 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);}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawPath(mPath, mPaint);}@Overridepublic 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線程阻塞)導致看上去不連續。本文通過增加觸摸點、減少重新整理地區後,手寫效率和效果得到明顯改善,當然通過軟體的進一步處理,手寫效果還能得到進一步改善,這需要通過軟體做插值處理(壓力值也可以考慮通過插值演算法算出來),具體在後面介紹。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.