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

這是一個自定義的view,通過在onTouchEvent時間中捕獲系統回調的觸摸點信息,然後再onDraw方法裡面刷新,可以明顯地感覺到線條很生硬,並且在手寫的過程中跟隨感很差,反應遲鈍,具體代碼如下:
<喎?/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();
}
通過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();
}
後記:
ndk-build提示make不是內部或外部命令
從官網下載了ndk,可是運行ndk-build竟然提示錯誤E:\android-ndk-r10d>ndk-build‘”E:\
ListView(列表視圖)+BaseAdapter(基礎適配器)=android滑動列表
首先,我們要建一個layout,裡面放一個ListView,長寬皆適應屏幕即可。 然後,我們要在這個ListView裡顯示東西,顯示什麼樣的東西
Android中進程間通信 (一)
在Android系統中,每一個應用程序都是由一些Activity和Service組成的,這些Activity和Service有可能運行在同一個進程中,也有可能運行在不同的
Android Navigation TabBar控件實現多彩標簽欄
先看看效果圖:源碼下載:Android Navigation TabBar控件實現多彩標簽欄代碼:MainActivity.javapackage com.bzu.gxs