I have a custom surfaceView which will paint the surface based on Touch event. When i draw something on this view it is working fine. But when i tried to erase the paint, nothing got erased. Please find the sample code snippet below:
public class MySurfaceView extends SurfaceView {
private static final String TAG = "FreeHandDrawing";
public static Canvas mCanvas;
SurfaceHolder holder;
private static Path path;
private Paint paint;
private ArrayList<Path> pathArrayList = new ArrayList<>();
private boolean freeHandMode;
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
freeHandMode = false;
path = new Path();
holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
setDrawingCacheEnabled(true);
this.setZOrderOnTop(true);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xFF22FF11);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(8);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(freeHandMode) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("Action", "Placed");
path.moveTo(event.getX(), event.getY());
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
Log.d("Action", "Moved");
path.lineTo(event.getX(), event.getY());
pathArrayList.add(path);
}
mCanvas = holder.lockCanvas();
if (mCanvas != null) {
if (pathArrayList.size() > 0) {
mCanvas.drawPath(pathArrayList.get(pathArrayList.size() - 1), paint);
}
holder.unlockCanvasAndPost(mCanvas);
} else {
Log.d(TAG, "Canvas is NULL");
}
}
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "On draw called");
}
public void eraseDrawing() {
pathArrayList.clear();
invalidate();
}
public void drawEnableDisable(boolean mode) {
freeHandMode = mode;
}
}
What is the problem with the code above ?
You should keep your drawing code in the onDraw method
#Override
protected void onDraw(Canvas canvas) {
// In case you want to delete/erase when path array/list is cleared.
if (pathArrayList.size() == 0) {
canvas.drawColor(0, Mode.CLEAR);
}
// In case, you want to draw all paths also in onDraw
for(Path path : pathArrayList) {
canvas.drawPath(path, paint);
}
}
When you clear the Path Array(list) and then upon calling invalidate(), onDraw gets triggered.
Note: You're supposed to call invalidate() at the end of onTouchEvent() to tell the system to update the screen. Calling invalidate() will get the framework to eventually call onDraw().
Also you should NOT use canvas obtained by lockCanvas as it will not be hardware accelerated. Instead you should use the canvas passed as an argument to onDraw() itself.
You can choose to make the system a bit smart by not having to draw the pull list of paths every frame and instead handle erases/redraws etc by using special flags. And otherwise just render the latest path generated by the most recent call to onTouchEvent().
Related
I have code that I need to improve.
Here's what's wrong: it's a little slow and choppy, meaning the lines aren't smooth and the drawing is a bit delayed.
public void touchStarted(Point point) {
if (null == drawingModePath) {
drawingModePath = new Path();
}
drawingModePath.moveTo(point.x, point.y);
}
public void touchMoved(Point point) {
drawingModePath.lineTo(point.x, point.y);
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
mainDrawingView.setImageBitmap(bitmap);
// Path
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
canvas.drawPath(drawingModePath, paint);
}
public void touchEnded(Point point) {
touchMoved(point);
}
In essence what this code does is drawing a path based on touchStarted, touchMoved, and touchEnded. If someone can help me optimize this, I'd be grateful. Perhaps if I don't recreate the bitmap each time touchMoved occurs? Not sure here... not sure... I use a UIBezierPath to perform this code on iOS and it's a bit faster (and smoother). Anyway, I come to you for help. Input appreciated.
you are recreating everything every move. that will affect the performance of drawing a lot. the event triggers every 8ms (or 16ms im not sure), imagine you are reinstantiating everything every 8ms? thats tough.
so this must be in the instantiation part
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
mainDrawingView.setImageBitmap(bitmap);
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
The touchMoved() should only record the new path and call the invalidate() to make the View redraw itself resulting in calling the draw method (onDraw()).
public void touchMoved(Point point) {
drawingModePath.lineTo(point.x, point.y);
invalidate();
}
and then implement onDraw() method to do the drawing
Heres how i do the drawing interface in one of my projects:
public class SignatureView extends View {
public SignatureView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
// instantiating my paint object
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path = new Path();
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld)
{
// this is where i initialize my canvas, because in constructor, the view is not completely instantiated yet, so getting the height and width there will result in null exception.
bitmap = Bitmap.createBitmap(xNew, yNew, Bitmap.Config.ARGB_8888);
background_canvas = new Canvas(bitmap);
}
#Override
protected void onDraw(Canvas canvas)
{
// draw the new path to a buffer canvas
background_canvas.drawPath(path, paint);
// put the buffer in the real canvas
canvas.drawBitmap(bitmap, 0, 0, paint);
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
//this is like your move event, it just records the new path every move.
int action = ev.getActionMasked();
if ( action == MotionEvent.ACTION_DOWN )
{
path.moveTo(ev.getX(), ev.getY());
}
else if ( action == MotionEvent.ACTION_MOVE )
{
path.lineTo(ev.getX(), ev.getY());
// call invalidate() to make the view redraw itself, resulting in calling the onDraw() method.
invalidate();
}
else if ( action == MotionEvent.ACTION_UP )
{
onDone.method();
}
return true;
}
public void clear()
{
background_canvas.drawColor(Color.WHITE);
path.reset();
invalidate();
}
interface OnDone{
void method();
}
public void setOnDone(OnDone new_onDone)
{
onDone = new_onDone;
}
OnDone onDone;
private Paint paint;
private Bitmap bitmap;
private Canvas background_canvas;
private Path path;
public Bitmap getBitmap()
{
return bitmap;
}
}
I am unable to get the canvas object in ontouch().Without the canvas I cannot draw a circle when touched.How can I draw any shape or image when touched
public class Board extends View implements View.OnTouchListener {
public Board(Context context) {
super(context);
Paint paint1 = new Paint();
paint1.setTextSize(50);
paint1.setColor(Color.WHITE);
View view=this;
view.setOnTouchListener(this);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRGB(200, 100, 0);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
final int action = MotionEventCompat.getActionMasked(event);
int pointer = MotionEventCompat.getActionIndex(event);
if (action == MotionEvent.ACTION_DOWN) {
canvas.drawCircle(70, 1100, 50, paint1);
}
return false;
}
To draw on canvas wherever you touch, you need a path to keep track of your touch points and path. Using the path object you can draw on canvas. See this answer Draw on touch
I am creating an app to draw free shapes on the surface screen but i could only draw separated points my problem is . i want the points to be connected to each other when i draw them not lifting my finger from the screen . i mean as long as i am touching the screen draw.here's my code so far.
public class SurfaceViewActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new DrawingView(this));
}
class DrawingView extends SurfaceView {
private final SurfaceHolder surfaceHolder;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private List<Point> pointsList = new ArrayList<Point>();
public DrawingView(Context context) {
super(context);
surfaceHolder = getHolder();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
// Add current touch position to the list of points
pointsList.add(new Point((int)event.getX(), (int)event.getY()));
// Get canvas from surface
Canvas canvas = surfaceHolder.lockCanvas();
// Clear screen
canvas.drawColor(Color.BLACK);
// Iterate on the list
for(int i=0; i<pointsList.size(); i++) {
Point current = pointsList.get(i);
// Draw points
canvas.drawPoint(current.x, current.y, paint);
}
// Release canvas
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
return false;
}
}
}
you can use this function to draw smooth lines
public void drawBrzierLine(Canvas mCanvas, float xi, float yi, float xd, float yd) {
Point start = new Point((int) xi, (int) yi);
Point end = new Point((int) xd, (int) yd);
Path mPath = new Path();
mPath.reset();
mPath.moveTo(start.x, start.y);
mPath.quadTo(start.x, start.y, end.x, end.y);
mCanvas.drawPath(mPath, mPaint);
}
In onTouchEvent(MotionEvent event) you only handle ACTION_DOWN. So this code will only run when you press down on the screen. Use ACTION_MOVE instead.
http://developer.android.com/reference/android/view/MotionEvent.html
Has anybody experience on drawing objects, by vertices e.g. Polygons and obtaining their surface and perimeter.
The geometry will be drawn by hand using vertices or coordinates similar to https://play.google.com/store/apps/details?id=de.hms.xconstruction and then shapes formed. I need to obtain the surface of these closed shapes.
Is there any available example on the net?
Thanks in advance.
I think the following piece of code could be a good start. It basically draws lines between all user touches:
public class TestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new DrawingView(this));
}
class DrawingView extends SurfaceView {
private final SurfaceHolder surfaceHolder;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private List<Point> pointsList = new ArrayList<Point>();
public DrawingView(Context context) {
super(context);
surfaceHolder = getHolder();
paint.setColor(Color.WHITE);
paint.setStyle(Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
// Add current touch position to the list of points
pointsList.add(new Point((int)event.getX(), (int)event.getY()));
// Get canvas from surface
Canvas canvas = surfaceHolder.lockCanvas();
// Clear screen
canvas.drawColor(Color.BLACK);
// Iterate on the list
for(int i=0; i<pointsList.size(); i++) {
Point current = pointsList.get(i);
// Draw points
canvas.drawCircle(current.x, current.y, 10, paint);
// Draw line with next point (if it exists)
if(i + 1 < pointsList.size()) {
Point next = pointsList.get(i+1);
canvas.drawLine(current.x, current.y, next.x, next.y, paint);
}
}
// Release canvas
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
return false;
}
}
Below is my new painter app for android.
However, it does not reflect previously drawn object.
(when i touch up the screen, it lost the shape)
To, reflect previously drawn object, I tried to use 'Bitmap.createBitmap' method but it does not work.
please help me.
public class CreativePainterActivity extends Activity {
//
//private MyView vw;
Paint mPaint;
//--Variables to store the current figure info
private float _currentStartX; //where mouse first pressed
private float _currentStartY;
private float _currentEndX; //where dragged to or released
private float _currentEndY;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
setContentView(new MyView(this));
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFFFFF00);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(8);
}
//view class
public class MyView extends View{
private Canvas mCanvas;
private Bitmap mBitmap;
private Paint mBitmapPaint;
Bitmap bm;
//private Paint mBitmapPaint;
public MyView(Context context){
super(context);
//ADDED
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mBitmap = Bitmap.createBitmap(metrics.widthPixels, metrics.heightPixels, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mCanvas.drawColor(0xFFFFFFFF);
bm = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
}
public void onDraw(Canvas canvas){
//canvas.drawColor(Color.LTGRAY);
canvas.drawBitmap(bm, 0, 0, mBitmapPaint);
canvas.drawLine(_currentStartX, _currentStartY, _currentEndX, _currentEndY, mPaint);
}
//Methods for touch events
public boolean onTouchEvent(MotionEvent event){
if (event.getAction() == MotionEvent.ACTION_DOWN){
_currentStartX=event.getX();
_currentStartY=event.getY();
return true;
}
if(event.getAction() == MotionEvent.ACTION_MOVE){
_currentEndX=event.getX();
_currentEndY=event.getY();
invalidate();
return true;
}
return true;
}
}//end of the class MyView
}//end of the class CreativePainterActivity
You will need to know a bit more about how views are drawn. Your views won't preserve whatever is there on, after you draw again. So, you should somehow save whatever was there before, and redraw the old stuff, along with the new changes.
A simpler solution, would be to save the previous drawings to a Bitmap, and then draw that Bitmap again on the canvas first, and add new stuff.
The flow
onDraw(){
drawBitmap(bmp);
drawOtherStuff();
bmp = saveOnScreenBitmap();
}
So, each time you need to save the last drawn bitmap, and re-draw it. Hope it's more clear now.
Some sample tutorials:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/FingerPaint.html
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/TouchPaint.html
http://www.tutorialforandroid.com/2009/06/drawing-with-canvas-in-android.html