Android如何用手指画一条平滑线
http://marakana.com/tutorials/android/2d-graphics-example.html
我在下面使用这个例子。 但是当我在屏幕上移动我的手指时,线条变成了单独的点。
我不确定是否可以加快绘图速度。 或者我应该用直线连接两个最后的点。 这两个解决scheme中的第二个似乎是一个不错的select,除非移动手指的速度非常快,否则将会有很长的一段直线,然后是尖锐的曲线。
如果还有其他的解决scheme,听到他们会很高兴。
提前感谢您的帮助。
正如你所提到的,一个简单的解决scheme就是简单地用直线连接点。 这是这样做的代码:
public void onDraw(Canvas canvas) { Path path = new Path(); boolean first = true; for(Point point : points){ if(first){ first = false; path.moveTo(point.x, point.y); } else{ path.lineTo(point.x, point.y); } } canvas.drawPath(path, paint); }
确保你从填充改变到中风:
paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2); paint.setColor(Color.WHITE);
另一种select是使用quadTo方法将点连接到iterpolation:
public void onDraw(Canvas canvas) { Path path = new Path(); boolean first = true; for(int i = 0; i < points.size(); i += 2){ Point point = points.get(i); if(first){ first = false; path.moveTo(point.x, point.y); } else if(i < points.size() - 1){ Point next = points.get(i + 1); path.quadTo(point.x, point.y, next.x, next.y); } else{ path.lineTo(point.x, point.y); } } canvas.drawPath(path, paint); }
这仍然导致一些尖锐的边缘。
如果你真的有雄心,你可以开始计算三次样条,如下所示:
public void onDraw(Canvas canvas) { Path path = new Path(); if(points.size() > 1){ for(int i = points.size() - 2; i < points.size(); i++){ if(i >= 0){ Point point = points.get(i); if(i == 0){ Point next = points.get(i + 1); point.dx = ((next.x - point.x) / 3); point.dy = ((next.y - point.y) / 3); } else if(i == points.size() - 1){ Point prev = points.get(i - 1); point.dx = ((point.x - prev.x) / 3); point.dy = ((point.y - prev.y) / 3); } else{ Point next = points.get(i + 1); Point prev = points.get(i - 1); point.dx = ((next.x - prev.x) / 3); point.dy = ((next.y - prev.y) / 3); } } } } boolean first = true; for(int i = 0; i < points.size(); i++){ Point point = points.get(i); if(first){ first = false; path.moveTo(point.x, point.y); } else{ Point prev = points.get(i - 1); path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y); } } canvas.drawPath(path, paint); }
另外,我发现您需要更改以下内容以避免重复的运动事件:
public boolean onTouch(View view, MotionEvent event) { if(event.getAction() != MotionEvent.ACTION_UP){ Point point = new Point(); point.x = event.getX(); point.y = event.getY(); points.add(point); invalidate(); Log.d(TAG, "point: " + point); return true; } return super.onTouchEvent(event); }
并将dx&dy值添加到Point类:
class Point { float x, y; float dx, dy; @Override public String toString() { return x + ", " + y; } }
这会产生平滑的线条,但有时必须使用循环来连接点。 而且,对于长时间的绘图会话来说,这将变成计算密集型计算。
希望有助于…有趣的东西玩弄。
编辑
我把一个快速的项目放在一起展示了这些不同的技术,包括Square的Suggessted签名实现。 享受: https : //github.com/johncarl81/androiddraw
这对你来说可能并不重要,但是我为解决这个问题挣扎了很多,我想分享一下,可能对别人有用。
提供的解决scheme@johncarl的教程非常适合绘制,但是它们为我的目的提供了一个限制。 如果您将手指从屏幕上取下并放回原位,此解决scheme将在最后一次点击与新点击之间划出一条线,使整个graphics始终保持连接。 所以我试图find一个解决scheme,最后我知道了!(对不起,如果听起来很明显,我是一个graphics初学者)
public class MainActivity extends Activity { DrawView drawView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set full screen view getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); drawView = new DrawView(this); setContentView(drawView); drawView.requestFocus(); } } public class DrawingPanel extends View implements OnTouchListener { private static final String TAG = "DrawView"; private static final float MINP = 0.25f; private static final float MAXP = 0.75f; private Canvas mCanvas; private Path mPath; private Paint mPaint; private LinkedList<Path> paths = new LinkedList<Path>(); public DrawingPanel(Context context) { super(context); setFocusable(true); setFocusableInTouchMode(true); this.setOnTouchListener(this); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setColor(Color.BLACK); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeWidth(6); mCanvas = new Canvas(); mPath = new Path(); paths.add(mPath); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { for (Path p : paths){ canvas.drawPath(p, mPaint); } } private float mX, mY; private static final float TOUCH_TOLERANCE = 4; private void touch_start(float x, float y) { mPath.reset(); mPath.moveTo(x, y); mX = x; mY = y; } private void touch_move(float x, float y) { float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2); mX = x; mY = y; } } private void touch_up() { mPath.lineTo(mX, mY); // commit the path to our offscreen mCanvas.drawPath(mPath, mPaint); // kill this so we don't double draw mPath = new Path(); paths.add(mPath); } @Override public boolean onTouch(View arg0, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touch_move(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touch_up(); invalidate(); break; } return true; } }
我拿着你的手指绘制的Android样本,并修改了一下,以存储每一个path,而不是最后一个! 希望它可以帮助别人!
干杯。
我尝试了几种方法来呈现运动事件的积分。 最后,我通过计算两点之间的中点得到最好的结果,并将列表中的点作为二次贝塞尔曲线的定位点(除了通过简单线连接到下一个中点的第一个和最后一个点)。
这给出了一个没有任何angular落的平滑曲线。 绘制的path不会触及列表中的实际点,而是经过每个中点。
Path path = new Path(); if (points.size() > 1) { Point prevPoint = null; for (int i = 0; i < points.size(); i++) { Point point = points.get(i); if (i == 0) { path.moveTo(point.x, point.y); } else { float midX = (prevPoint.x + point.x) / 2; float midY = (prevPoint.y + point.y) / 2; if (i == 1) { path.lineTo(midX, midY); } else { path.quadTo(prevPoint.x, prevPoint.y, midX, midY); } } prevPoint = point; } path.lineTo(prevPoint.x, prevPoint.y); }
我有非常类似的问题。 当你调用onTouch方法时,还应该使用方法(在onTouch(MotionEvent事件)中)
event.getHistorySize();
和类似的东西
int histPointsAmount = event.getHistorySize(); for(int i = 0; i < histPointsAmount; i++){ // get points from event.getHistoricalX(i); // event.getHistoricalY(i); and use them for your purpouse }
最近我不得不做一些修改,现在已经开发了我认为是最好的解决scheme,因为它有三件事情:
- 它可以让你画不同的线条
- 它适用于较大的笔触,而不使用复杂的三次样条
- 它比这里的很多解决scheme要快,因为
canvas.drawPath()
方法在for循环之外 ,所以不会被多次调用。
public class DrawView extends View implements OnTouchListener { private static final String TAG = "DrawView"; List<Point> points = new ArrayList<Point>(); Paint paint = new Paint(); List<Integer> newLine = new ArrayList<Integer>(); public DrawView(Context context, AttributeSet attrs){ super(context, attrs); setFocusable(true); setFocusableInTouchMode(true); setClickable(true); this.setOnTouchListener(this); paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(20); } public void setColor(int color){ paint.setColor(color); } public void setBrushSize(int size){ paint.setStrokeWidth((float)size); } public DrawView(Context context) { super(context); setFocusable(true); setFocusableInTouchMode(true); this.setOnTouchListener(this); paint.setColor(Color.BLUE); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(20); } @Override public void onDraw(Canvas canvas) { Path path = new Path(); path.setFillType(Path.FillType.EVEN_ODD); for (int i = 0; i<points.size(); i++) { Point newPoint = new Point(); if (newLine.contains(i)||i==0){ newPoint = points.get(i) path.moveTo(newPoint.x, newPoint.y); } else { newPoint = points.get(i); path.lineTo(newPoint.x, newPoint.y); } } canvas.drawPath(path, paint); } public boolean onTouch(View view, MotionEvent event) { Point point = new Point(); point.x = event.getX(); point.y = event.getY(); points.add(point); invalidate(); Log.d(TAG, "point: " + point); if(event.getAction() == MotionEvent.ACTION_UP){ // return super.onTouchEvent(event); newLine.add(points.size()); } return true; } } class Point { float x, y; @Override public String toString() { return x + ", " + y; } }
这也有效,只是不太好
import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.util.*; public class DrawView extends View implements OnTouchListener { private static final String TAG = "DrawView"; List<Point> points = new ArrayList<Point>(); Paint paint = new Paint(); List<Integer> newLine = new ArrayList<Integer>(); public DrawView(Context context, AttributeSet attrs){ super(context, attrs); setFocusable(true); setFocusableInTouchMode(true); this.setOnTouchListener(this); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } public DrawView(Context context) { super(context); setFocusable(true); setFocusableInTouchMode(true); this.setOnTouchListener(this); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override public void onDraw(Canvas canvas) { for (int i = 0; i<points.size(); i++) { Point newPoint = new Point(); Point oldPoint = new Point(); if (newLine.contains(i)||i==0){ newPoint = points.get(i); oldPoint = newPoint; } else { newPoint = points.get(i); oldPoint = points.get(i-1); } canvas.drawLine(oldPoint.x, oldPoint.y, newPoint.x, newPoint.y, paint); } } public boolean onTouch(View view, MotionEvent event) { Point point = new Point(); point.x = event.getX(); point.y = event.getY(); points.add(point); invalidate(); Log.d(TAG, "point: " + point); if(event.getAction() == MotionEvent.ACTION_UP){ // return super.onTouchEvent(event); newLine.add(points.size()); } return true; } } class Point { float x, y; @Override public String toString() { return x + ", " + y; } }
它可以让你合理地画线,唯一的问题是如果你使线更粗,这使得画出的线条看起来有点奇怪,实际上,我会build议使用第一个反正。
如果你想要简单:
public class DrawByFingerCanvas extends View { private Paint brush = new Paint(Paint.ANTI_ALIAS_FLAG); private Path path = new Path(); public DrawByFingerCanvas(Context context) { super(context); brush.setStyle(Paint.Style.STROKE); brush.setStrokeWidth(5); } @Override protected void onDraw(Canvas c) { c.drawPath(path, brush); } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: path.moveTo(x,y); break; case MotionEvent.ACTION_MOVE: path.lineTo(x, y); break; default: return false; } invalidate(); return true; } }
在这个活动中:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new DrawByFingerCanvas(this)); }
结果:
要删除所有图纸只需旋转屏幕。
具有ACTION_MOVE
运动事件可以将单个对象内的多个运动样本一起批处理。 最新的指针坐标可以使用getX(int)和getY(int)。 早期的批处理坐标是使用getHistoricalX(int, int)
和getHistoricalY(int, int)
。 使用它们来build立path使得它更平滑:
int historySize = event.getHistorySize(); for (int i = 0; i < historySize; i++) { float historicalX = event.getHistoricalX(i); float historicalY = event.getHistoricalY(i); path.lineTo(historicalX, historicalY); } // After replaying history, connect the line to the touch point. path.lineTo(eventX, eventY);
这是来自Square的一个很好的教程: http : //corner.squareup.com/2010/07/smooth-signatures.html
在MotionEvent
可能有更多的信息比你意识到可以提供一些数据。
链接中的示例将忽略事件中包含的历史触点。 请参阅MotionEvent
文档顶部附近的“批处理”部分: http : //developer.android.com/reference/android/view/MotionEvent.html除此之外,用线连接点可能不是一个坏主意。
我有这个问题,我画了一个点,而不是一条线。 你应该先创build一个path来保持你的路线。 仅在第一次触摸事件时调用path.moveto。 然后在canvas上绘制path,然后在完成(path.reset)之后重置或倒回path…