Android 系列 5.4徒手绘制平滑曲线
2017-01-10 09:25
429 查看
5.4徒手绘制平滑曲线
问题
您希望允许用户绘制平滑的曲线,如手绘贝塞尔曲线,法律签名等。
解
使用仔细编写的OnTouchListener创建自定义视图,处理输入的速度比代码可以处理的速度快的情况;将结果保存在数组中,并在onDraw()中绘制它们。
讨论
此代码最初由Square Inc.的Eric Burke编写,用于在人们使用Square应用捕获信用卡购买时捕获签名。为了在法律上可接受作为购买意图的证明,所捕获的签名必须具有良好的质量。 Square已经把这个代码放在Apache Software License 2.0下面,但是不能提供它作为这个菜谱的一部分。
我已经适应的签名代码用于JabaGator,我的非常简单的,通用的绘图程序的Java桌面和Android,但事实上,名称rhymes与着名的插图程序从Adobe是,当然,纯粹巧合。
埃里克的初始,“通过书”绘图代码工作,但非常干燥,非常缓慢。根据调查,Square了解到Android的图形层以“批次”发送触摸事件,当它无法单独快速传递时。传递到onTouchEvent()的每个MotionEvent可能包含多个触摸坐标,与自上次onTouchEvent()调用以来捕获的数量一样多。要绘制一条平滑的曲线,你必须得到所有的点。你可以使用TouchEvent方法getHistorySize()的坐标数,迭代该计数,并调用getHistoricalX(int)和getHistoricalY(int)来获取点的位置(见例5-8)。
实例5-8。绘制所有的点
这提供了显着的改进,但是它仍然是太慢,人们绘制与 - 许多非极客将等待绘图代码赶上他们的手指,如果它不够快速绘制足够!问题是,一个简单的解决方案在每个线段之后调用invalidate(),这是正确的但很慢,因为它强制Android重绘整个屏幕。这个问题的解决方案是调用invalidate()只是你绘制线段的区域,并涉及一些算术来获得区域正确;请参见示例5-9中的expandDirtyRect()方法。这里是Eric对脏区算法的描述:
1.“创建一个表示脏区域的矩形。
2.“从ACTION_DOWN事件将四个角的点设置为X和Y坐标。
3.“对于ACTION_MOVE和ACTION_UP,展开矩形以包含新点。 (不要忘记历史坐标!)
4.“只传递脏矩形为invalidate()。 Android不会重绘其余的。
这组步骤使绘图代码响应,并且应用程序可用。
例5-9显示了我的版本的最终代码。我有几个OnTouchListener,一个用于绘制曲线,一个用于选择对象,一个用于绘制矩形,等等。该代码目前不完整,但曲线绘图部分工作得很好。
实例5-9。 DrawingView.java
图5-3显示了JabaGator正在运行,显示我尝试清晰的笔迹(不要担心,这不是我的法律签名)。
图5-3。触摸绘图示例
这提供了良好的拉丝性能和平滑的曲线。将曲线捕获到绘图数据模型中的代码未显示,因为它是特定于应用程序的。
问题
您希望允许用户绘制平滑的曲线,如手绘贝塞尔曲线,法律签名等。
解
使用仔细编写的OnTouchListener创建自定义视图,处理输入的速度比代码可以处理的速度快的情况;将结果保存在数组中,并在onDraw()中绘制它们。
讨论
此代码最初由Square Inc.的Eric Burke编写,用于在人们使用Square应用捕获信用卡购买时捕获签名。为了在法律上可接受作为购买意图的证明,所捕获的签名必须具有良好的质量。 Square已经把这个代码放在Apache Software License 2.0下面,但是不能提供它作为这个菜谱的一部分。
我已经适应的签名代码用于JabaGator,我的非常简单的,通用的绘图程序的Java桌面和Android,但事实上,名称rhymes与着名的插图程序从Adobe是,当然,纯粹巧合。
埃里克的初始,“通过书”绘图代码工作,但非常干燥,非常缓慢。根据调查,Square了解到Android的图形层以“批次”发送触摸事件,当它无法单独快速传递时。传递到onTouchEvent()的每个MotionEvent可能包含多个触摸坐标,与自上次onTouchEvent()调用以来捕获的数量一样多。要绘制一条平滑的曲线,你必须得到所有的点。你可以使用TouchEvent方法getHistorySize()的坐标数,迭代该计数,并调用getHistoricalX(int)和getHistoricalY(int)来获取点的位置(见例5-8)。
实例5-8。绘制所有的点
// in onTouchEvent(TouchEvent): for (int i=0; i < event.getHistorySize(); i++) { float historicalX = event.getHistoricalX(i); float historicalY = event.getHistoricalY(i); ... add point (historicalX, historicalY) to your path ... } ... add point (eventX, eventY) to your path ...
这提供了显着的改进,但是它仍然是太慢,人们绘制与 - 许多非极客将等待绘图代码赶上他们的手指,如果它不够快速绘制足够!问题是,一个简单的解决方案在每个线段之后调用invalidate(),这是正确的但很慢,因为它强制Android重绘整个屏幕。这个问题的解决方案是调用invalidate()只是你绘制线段的区域,并涉及一些算术来获得区域正确;请参见示例5-9中的expandDirtyRect()方法。这里是Eric对脏区算法的描述:
1.“创建一个表示脏区域的矩形。
2.“从ACTION_DOWN事件将四个角的点设置为X和Y坐标。
3.“对于ACTION_MOVE和ACTION_UP,展开矩形以包含新点。 (不要忘记历史坐标!)
4.“只传递脏矩形为invalidate()。 Android不会重绘其余的。
这组步骤使绘图代码响应,并且应用程序可用。
例5-9显示了我的版本的最终代码。我有几个OnTouchListener,一个用于绘制曲线,一个用于选择对象,一个用于绘制矩形,等等。该代码目前不完整,但曲线绘图部分工作得很好。
实例5-9。 DrawingView.java
// This code is dual-licensed under Creative Commons and Apache Software License 2.0 public class DrawingView extends View { private static final float STROKE_WIDTH = 5f; /** Need to track this so the dirty region can accommodate the stroke. **/ private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2; private Paint paint = new Paint(); private Path path = new Path(); /** * Optimizes painting by invalidating the smallest possible area. */ private float lastTouchX; private float lastTouchY; private final RectF dirtyRect = new RectF(); final OnTouchListener selectionAndMoveListener = // not shown; final OnTouchListener drawRectangleListener = // not shown; final OnTouchListener drawOvalListener = // not shown; final OnTouchListener drawPolyLineListener = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // Log.d("jabagator", "onTouch: " + event); float eventX = event.getX(); float eventY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: path.moveTo(eventX, eventY); lastTouchX = eventX; lastTouchY = eventY; // No end point yet, so don't waste cycles invalidating. return true; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: // Start tracking the dirty region. resetDirtyRect(eventX, eventY); // When the hardware tracks events faster than // they can be delivered to the app, the // event will contain a history of those skipped points. int historySize = event.getHistorySize(); for (int i = 0; i < historySize; i++) { float historicalX = event.getHistoricalX(i); float historicalY = event.getHistoricalY(i); expandDirtyRect(historicalX, historicalY); path.lineTo(historicalX, historicalY); } // After replaying history, connect the line to the touch point. path.lineTo(eventX, eventY); break; default: Log.d("jabagator", "Unknown touch event " + event.toString()); return false; } // Include half the stroke width to avoid clipping. invalidate( (int) (dirtyRect.left - HALF_STROKE_WIDTH), (int) (dirtyRect.top - HALF_STROKE_WIDTH), (int) (dirtyRect.right + HALF_STROKE_WIDTH), (int) (dirtyRect.bottom + HALF_STROKE_WIDTH)); lastTouchX = eventX; lastTouchY = eventY; return true; } /** * Called when replaying history to ensure the dirty region * includes all points. */ private void expandDirtyRect(float historicalX, float historicalY) { if (historicalX < dirtyRect.left) { dirtyRect.left = historicalX; } else if (historicalX > dirtyRect.right) { dirtyRect.right = historicalX; } if (historicalY < dirtyRect.top) { dirtyRect.top = historicalY; } else if (historicalY > dirtyRect.bottom) { dirtyRect.bottom = historicalY; } } /** * Resets the dirty region when the motion event occurs. */ private void resetDirtyRect(float eventX, float eventY) { // The lastTouchX and lastTouchY were set when the ACTION_DOWN // motion event occurred. dirtyRect.left = Math.min(lastTouchX, eventX); dirtyRect.right = Math.max(lastTouchX, eventX); dirtyRect.top = Math.min(lastTouchY, eventY); dirtyRect.bottom = Math.max(lastTouchY, eventY); } }; /** DrawingView Constructor */ public DrawingView(Context context, AttributeSet attrs) { super(context, attrs); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeWidth(STROKE_WIDTH); setMode(MotionMode.DRAW_POLY); } public void clear() { path.reset(); // Repaints the entire view. invalidate(); } @Override protected void onDraw(Canvas canvas) { canvas.drawPath(path, paint); } /** * Sets the DrawingView into one of several modes, such * as "select" mode (e.g., for moving or resizing objects), * or "Draw polyline" (smooth curve), "draw rectangle", etc. */ private void setMode(MotionMode motionMode) { switch(motionMode) { case SELECT_AND_MOVE: setOnTouchListener(selectionAndMoveListener); break; case DRAW_POLY: setOnTouchListener(drawPolyLineListener); break; case DRAW_RECTANGLE: setOnTouchListener(drawRectangleListener); break; case DRAW_OVAL: setOnTouchListener(drawOvalListener); break; default: throw new IllegalStateException("Unknown MotionMode " + motionMode); } } }
图5-3显示了JabaGator正在运行,显示我尝试清晰的笔迹(不要担心,这不是我的法律签名)。
图5-3。触摸绘图示例
这提供了良好的拉丝性能和平滑的曲线。将曲线捕获到绘图数据模型中的代码未显示,因为它是特定于应用程序的。
相关文章推荐
- Android-多点绘制平滑曲线
- android游戏引擎andengine学习系列三:绘制游戏虚拟摇杆
- Android游戏开发中绘制游戏触摸轨迹的曲线图
- Android之使用AchartEngineActivity引擎绘制柱状图、曲线图
- Android自定义组件系列【9】——Canvas绘制折线图
- Sketch Nyquist plot 徒手绘制Nyquist 曲线
- 绘制平滑曲线
- Android之使用AchartEngineActivity引擎绘制柱状图、曲线图
- matlab绘制平滑曲线
- android绘制一个圆柱的螺线曲线
- 在Android中如何绘制光滑曲线(一)
- 使用JFreeChart在网页上绘制平滑曲线
- Android 绘制动态的曲线图
- [Android]Android高级UI开发系列教程(二) - Android绘制教程
- AChartEngine实现Android实时曲线绘制
- android游戏引擎andengine学习系列四:绘制特效的动画文字
- Android游戏开发系列教程第二讲(基本图形绘制)
- AChartEngine实现Android实时曲线绘制
- Android 动态绘制曲线等各种图形
- 【自定义控件系列四】android绘制实战(一)通过Canvas+Path+Paint组合绘制图表