How to draw with precision (e.g. a line) in Android? - android

I've been trying to make a drawing canvas in my app, but I'm unable to draw the points precisely. I'm using an OnTouchListener and I add every point in the historical pointer to the canvas and paint it with black.
Unfortunately, it's not working because it's not drawing all the points touched by the user (for example, when tracing a line). I'll show you an example:
The code I'm using is the following:
public class DrawView extends View implements OnTouchListener {
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
}
public DrawView(Context context,AttributeSet attrs) {
super(context,attrs);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
}
public DrawView(Context context,AttributeSet attrs,int defStyle) {
super(context,attrs,defStyle);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
}
#Override
public void onDraw(Canvas canvas) {
for (Point point : points) {
canvas.drawCircle(point.x, point.y, 12, paint);
}
}
public boolean onTouch(View view, MotionEvent event) {
Point point = new Point();
final int historySize = event.getHistorySize();
final int pointerCount = event.getPointerCount();
for (int h = 0; h < historySize; h++) {
for (int p = 0; p < pointerCount; p++) {
point.x=event.getHistoricalX(p, h);
point.y=event.getHistoricalY(p, h);
points.add(point);
invalidate();
Log.d("debug","pointer: "+
event.getPointerId(p)+" "+ event.getHistoricalX(p, h)+" "+ event.getHistoricalY(p, h));
}
}
/*point.x = (int)event.getX();
point.y = (int)event.getY();*/
Log.d("debug", "really saved: "+
event.getX()+" "+ event.getY());
//points.add(point);
//invalidate();
return true;
}
}
class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
Any ideas?
Thank you in advance

The problem is that you are only drawing points you get touch events for. If the user moves quickly you only get some of the points. The solution is to draw lines between consecutive points rather than just the points themselves.

You are missing many points here, you should capture all points here rather than capturing historical points only.
Check this -
http://www.codeproject.com/Articles/458042/Touch-handling-in-Android

Related

Custom view doesn't draw Bitmap

I´m trying to display a moving cloud over a blue sky. The blue background is displayed but the cloud doesn't appear. I've tried the different approaches in other questions here but nothing works. My code is:
public class CloudBackground extends View {
Bitmap cloud;
int x = 0;
int y = 0;
Paint paint = new Paint();
Rect rectangle = new Rect(0,0,100,100);
public CloudBackground(Context context,AttributeSet attrs) {
super(context,attrs);
cloud = BitmapFactory.decodeResource(getResources(),R.drawable.cloud1);
}
#Override
protected void onDraw (Canvas canvas){
super.onDraw(canvas);
Rect back = new Rect();
back.set(0,0,canvas.getWidth(), canvas.getHeight());
Paint pBlue = new Paint();
pBlue.setStyle(Paint.Style.FILL);
pBlue.setColor(Color.CYAN);
canvas.drawRect(back, pBlue);
drawCloud(x,y,canvas);
if (x < canvas.getWidth())
x = x + 10;
else {
y = y + 10;
x = 0;
}
invalidate();
}
private void drawCloud(int x2, int y2, Canvas canvas) {
canvas.drawBitmap(cloud, x2, y2,paint);
}
Ok. Try this:
public CloudBackground(Context context,AttributeSet attrs) {
super(context,attrs);
cloud = BitmapFactory.decodeResource(context.getApplicationContext().getResources(),R.drawable.cloud1);
}
The issue was finally solved using a.jpg instead a .png. As simple as that and with no reason.

Why it draws slow? or strange?

I'm trying to do a view that allows the user to "draw" on it. Right now what I do are paths and then I connect it with a line, but don't work properly, it work slow and make things "strange". You can see that on this video http://youtu.be/PUSUTFhDPrM , sorry, it goes a bit fast, but you can see what I'm talking about.
My actual code is:
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
private List<List<Point>> _paths = new ArrayList<List<Point>>();
private List<Point> _lastPath;
private Paint _paint = new Paint();
private Path _path = new Path();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
setOnTouchListener(this);
_paint.setColor(Color.BLACK);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeWidth(5);
_paint.setAntiAlias(true);
}
public DrawView(Context context, AttributeSet attrs) {
super( context, attrs );
setFocusable(true);
setFocusableInTouchMode(true);
setOnTouchListener(this);
_paint.setColor(Color.BLACK);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeWidth(5);
_paint.setAntiAlias(true);
}
public DrawView(Context context, AttributeSet attrs, int defStyle) {
super( context, attrs, defStyle );
setFocusable(true);
setFocusableInTouchMode(true);
setOnTouchListener(this);
_paint.setColor(Color.BLACK);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeWidth(5);
_paint.setAntiAlias(true);
}
#Override
protected void onDraw(Canvas canvas) {
for (List<Point> pointsPath : _paths) {
_path.reset();
boolean first = true;
for (int i = 0; i < pointsPath.size(); i += 2) {
Point point = pointsPath.get(i);
if (first) {
first = false;
_path.moveTo(point.x, point.y);
} else if (i < pointsPath.size() - 1) {
Point next = pointsPath.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 boolean onTouch(View view, MotionEvent event) {
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
Log.d(TAG, "point: " + point);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
_lastPath = new ArrayList<Point>();
_lastPath.add(point);
_paths.add(_lastPath);
break;
case MotionEvent.ACTION_MOVE:
_lastPath.add(point);
break;
}
invalidate();
return true;
}
private class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
public void changePaint(int Stroke, int color){
_path.reset();
_paint.setColor(color);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeWidth(Stroke);
_paint.setAntiAlias(true);
}
}
What I want is know if there is a better method to allow the user to "draw " with the finger or what can I improve to remove the slowest part of this code.
Probably the problem is that you perform such action on UI thread, try to do it on background thread.
Your draw operation is fine. The worse thing to do when drawing is instanciating new objects and you don't do it.
To comment #Atermis's answer, actually it doesn't make much sense to "draw in the background". Drawing in Android happens on the UI Thread, if you had heavy computations to draw, then yes, it could be useful, but here something simpler could be the solution : a double buffer.
Either you could use a surface view and lockCanvas or you could draw in a memory buffer and then display it in a single operation as explained here : http://www.mail-archive.com/android-beginners#googlegroups.com/msg03172.html.
You may want to read about batching of the MotionEvent here: http://developer.android.com/reference/android/view/MotionEvent.html
Basically you can get a list of all coordinates between current and last X,Y by using getHistoricalX and getHistoricalY.

ImageView: onDraw method draws "behind" the image background, not at the top

Here is the code of a simple extension of an ImageView that allow the user to draw with the finger.
public class MyImageView extends ImageView {
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onDraw(Canvas canvas) {
for (Point point : points) {
canvas.drawCircle(point.x, point.y, 5, paint);
// Log.d(TAG, "Painting: "+point);
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
Log.d("", "point: " + point);
return true;
}
class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
}
This work pretty well.
Now, I want to use this code to draw at the top of a Bitmap.
I used:
MyImageView ivPic = (MyImageView) dialog.findViewById(R.id.ivPic);
ivPic.setImageBitmap(picture);
But the drawing is drawn "behind" the Bitmap:
Do you have any idea how I can draw at the "top" of the Bitmap?
I've changed the order in onDraw method, I put super.onDraw(canvas); at the beginning and I think it works now
override and use dispatchDraw Method, i used it to draw over views
I answer my own question, just found the answer:
ivPic.setBackgroundDrawable(new BitmapDrawable(getResources(),picture));
Don't know if that's the best practice but that works for me...
Change the sequence: first super.OnDraw(canvas) then your paint stuff
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Point point : points) {
canvas.drawCircle(point.x, point.y, 5, paint);
// Log.d(TAG, "Painting: "+point);
}
}

android line draw

I want to draw lines on the screen but my application is drawing only dotted. So what should I add in my code?
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
}
#Override
public void onDraw(Canvas canvas) {
for (Point point : points) {
canvas.drawCircle(point.x, point.y, 5, paint);
// Log.d(TAG, "Painting: "+point);
}
}
public boolean onTouch(View view, MotionEvent event) {
// if(event.getAction() != MotionEvent.ACTION_DOWN)
// return super.onTouchEvent(event);
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
Log.d(TAG, "point: " + point);
return true;
}
}
class Point
{
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
Edit
Use drawLine or drawPath instead of drawCircle.
I would suggest you to take a look on the Fingerpaint example of the API Demos
Use drawLine bewteen 2 sets of points
For smoother lines, get all the historic events in onTouch and process them first
for faster/smoother, invalidate only the rect where points have changes
Read more here

How to get current canvas?

I have DrawView. If I touch this view it draws small circles. I wont to draw circles but not to touch view - with help function "setPoints". What I do:
package com.samples;
import ...
public class DrawView extends View {
ArrayList<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
private int pSize = 5;
private int pColor = Color.BLACK;
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.setOnTouchListener(this);
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
}
return true;
}
});
requestFocus();
}
#Override
public void onDraw(Canvas canvas) {
for (Point point : points) {
canvas.drawCircle(point.x, point.y, pSize, paint);
}
}
public void setPoints(Float xP, Float yP)
{
Point point = new Point();
point.x = xP;
point.y = yP;
points.add(point);
postInvalidate();
}
}
class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
Please tell me, how get canvas out setPoints function?
Update:
Wow, it's really interesting problem. My DrawView contains in HorizontalScrollView. Because if I set in this DrawView right coordinates, no one knows where are drawable circles.
You can't. The canvas is managed by the system and is passed to your onDraw(). I don't understand why you'd need it outside of there. Just redeclare setPoints like this
public void setPoints(Canvas canvas, Float xP, Float Yp)
You can keep a cache of the previous drawings (or store the previous points)
Try declaring canvas2 as a public variable in the DrawView class.
You draw your circles in onDraw(). That's the way View is supposed to work (technically it's actually in the draw() method but we'll overlook that). In setPoints(), set the points of the circle in variables within the class scope, call invalidate(), then draw the circle like that in onDraw(). If you follow this method, you're following the class flow that the view was designed for.

Categories

Resources