Why it draws slow? or strange? - android

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.

Related

How to Convert a variable to a constant

I am new to android & java. This might be very simple but I'm stuck... I am creating an app where by clicking on an "add" button a square is added in some default position on a canvas. You can then move and resize the square. Once you finish moving/ reshaping, my intention is for the square to maintain its new size/ position and for the user to be able to click on the add button where a new square will appear in the original default position. You can then again move / reshape the second square. And eventually add as many squares as you like (each unique).
I have managed to add the first square and i am also able to move/ reshape. I also created an array list to store the squares. My issue is that as soon as i add the second square, the shape and position of the first square are reset to the default position and size. And if i move the second square, so does the first one. So effectively its as if i end up with a stacked set of squares that all move and reshape at the same time.
The question is, how do i convert the variable information of position/ size to a constant before i add the square to the array?
I have the following in my main activity
addBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(addTimer != null) {
addTimer.cancel();
}
if (addclickcount>0) {
addBoxtoList();
}
addBox();
addclickcount++;
}
});
public void addBox(){ mCustomView.drawRoundRect();}
public void addBoxtoList(){mCustomView.addBoxtoList();}
Then in CustomView
public class CustomView extends View {
private ArrayList<Box> boxesArray = new ArrayList<>();
private RectF mRectSquare;
private Paint mPaintSquare;
private static RectF aRectSquare;
private static Paint aPaintSquare;
private int mSquareLeft, mSquareTop, dx, dy;
int colorSelected;
public CustomView(Context context) {
super(context);
init(null);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init (#Nullable AttributeSet set){
mRectSquare = new RectF();
mPaintSquare = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintSquare.setColor(Color.BLUE);
mPaintSquare.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintSquare.setAlpha(85);
}
public void drawRoundRect(){
if (General.addBoxCheck == true) {
dx = 200;
dy = 200;
mSquareLeft = scrwd - dx - 50;
mSquareTop = 50;
General.addBoxCheck = false;
}
mRectSquare.left = mSquareLeft;
mRectSquare.top = mSquareTop;
mRectSquare.right = mRectSquare.left + dx;
mRectSquare.bottom = mRectSquare.top + dy;
postInvalidate();
}
public void addBoxtoList(){
boxid = boxesArray.size() + 1;
aRectSquare = mRectSquare;
aPaintSquare = mPaintSquare;
Box boxa = new Box(boxid, aRectSquare, aPaintSquare);
boxesArray.add(boxa);
//postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRoundRect(mRectSquare, 15, 15, mPaintSquare);
if(boxesArray != null) {
RectF rectF = new RectF();
Paint paint = new Paint();
for (int i = 0; i < boxesArray.size(); i++) {
paint = boxesArray.get(i).getPaint();
rectF = boxesArray.get(i).getRect();
canvas.drawRoundRect(rectF, 15, 15, paint);
}
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean value = super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
return true;
}
case MotionEvent.ACTION_MOVE:{
int x = (int) event.getX();
int y = (int) event.getY();
mSquareLeft = x;
mSquareTop = y;
postInvalidate();
return value;
}
}
return value;
}
Thanks
The question seems quite vague, if the values are changing then it means that you changed the values. If you don’t change the values of the array then essentially it should be a "constant". My assumption is that you are overwriting the array and therefore the value is changing. It would help to see your code.
Check the index of the stored square shapes and verify that you're not overwriting on any of the previous indexes.

Unable to create a growing view

Im using the following code to animate the view when it is drawn...
public class MyView extends View {
int iCurStep = 0;// current animation step
class Points {
float x, y;
Points(float _x, float _y) {
x = _x;
y = _y;
}
}
Points[] drawPaths = {new Points(-75, 0), new Points(20, 60), new Points(60, 20)};
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
PathMeasure pm = new PathMeasure(path, false);
if (iCurStep <= 20) {
pm.getPosTan(fSegmentLen * iCurStep, afP, null);
path.moveTo(drawPaths[0].x, drawPaths[0].y);
for (int i = 1; i < drawPaths.length; i++) {
path.lineTo(drawPaths[i].y, drawPaths[i].y);
}
canvas.drawPath(path, paint);
iCurStep++;
} else {
iCurStep = 0;
}
}
}
What Im expecting is that, it has to be a growing view...I mean the view has to grow as it is being drawn...But Im not able to produce to do so...How can I be able to sort this out?
Use a ObjectAnimator.
Create your view as normal, but then create an ObjectAnimator (docs: https://developer.android.com/reference/android/animation/ObjectAnimator.html). You'll do something like this in your fragment or activity (you can make it in XML as well). Essentially you create an ObjectAnimator for an object with a property string. The property string must have a camelCase setter. So if you wanted to call view.setScaleX() as the property you were going to modify, you'd need to set the property string to 'scaleX'. Here's a simple example.
View growMe = new View(args);
float startSize = 1.0f;
float endSize = 2.0f;
ObjectAnimator grower = ObjectAnimator.ofFloat(growMe, "scaleX", startSize, endSize);
int durationMs = 1000;
grower.setDuration (durationMs)
grower.start();
The one other thing you'll need to do is add an AnimationListener to your View that actually adjusts the size to be the correct as the end of the animation as (I believe--I might be wrong on this part), the view will re-size after the animation ends.
I hope that helps!

Draw on the image instead of image view in android

I am working on a drawing board app. I would like to implement a "pen" that draw on the image.
And so far I have create a custom imageview:
public class CustomDraw extends ImageView {
private int color = Color.BLACK;
private float width = 4f;
private List<Holder> holderList = new ArrayList<Holder>();
private class Holder {
Path path;
Paint paint;
Holder(int color, float width) {
path = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(width);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
}
}
public CustomDraw(Context context) {
super(context);
init();
}
public CustomDraw(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomDraw(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
holderList.add(new Holder(color, width));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Holder holder : holderList) {
canvas.drawPath(holder.path, holder.paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
holderList.add(new Holder(color,width));
holderList.get(holderList.size() - 1).path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
holderList.get(holderList.size() - 1).path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
break;
default:
return false;
}
invalidate();
return true;
}
public void resetPaths() {
for (Holder holder : holderList) {
holder.path.reset();
}
invalidate();
}
public void setBrushColor(int color) {
this.color = color;
}
public void setWidth(float width) {
this.width = width;
}
}
The problem is , how can I draw on the image actually but not the imageview? That means, I should not able to draw outside the image, and when I zoom the view, the content should be zoom accordingly. I have found and attempt using the extend drawable but it seems I can not draw at run time.
Thanks
You should possibly use SurfaceView instead. It is more advanced compared to ImageView.
Refer to this for drawing: http://examples.javacodegeeks.com/android/core/ui/surfaceview/android-surfaceview-example/
This is for zoom:
http://android-innovation.blogspot.co.nz/2013/07/how-to-implement-pinch-and-pan-zoom-on.html

How to draw with precision (e.g. a line) in 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

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