Reputation: 3120
My Activity Class file:
package com.drawing.test;
import android.app.Activity;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;
public class TstActivity extends Activity implements OnTouchListener
{
float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
public static boolean action=false;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint;
Drawer mDrawer;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout mLinearLayout = (LinearLayout) findViewById(R.id.drawView);
mLinearLayout.setOnTouchListener((OnTouchListener) this);
mLinearLayout.addView(new Drawer(this));
}
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
action=false;
//v.invalidate();
return true;
case MotionEvent.ACTION_MOVE:
x1=x2;
y1=y2;
x2 = event.getX();
y2 = event.getY();
v.invalidate();
action=true;
return true;
case MotionEvent.ACTION_UP:
x2 = event.getX();
y2 = event.getY();
v.invalidate();
action=true;
return true;
}
return false;
}
public class Drawer extends View
{
public Drawer(Context context)
{
super(context);
mBitmap = Bitmap.createBitmap(400, 800, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mBitmapPaint.setColor(Color.MAGENTA);
invalidate();
}
protected void onDraw(Canvas canvas)
{
if(x1==x2 && y1==y2)return;
Paint p = new Paint();
// Canvas mCanvas1=new Canvas(mBitmap);
p.setColor(Color.parseColor("#7CFC00"));
canvas.drawBitmap(mBitmap, 0, 0, p);
// canvas.drawLine(x1, y1, x2 , y2, p);
p.setColor(Color.RED);
// mCanvas1.drawLine(x1, y1, x2, y2,p);
if(action==true)mCanvas.drawLine(x1, y1, x2, y2, mBitmapPaint);
}
}
}
My Layout XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/drawView">
</LinearLayout>
When I try this out on emulator, the line breaks in middle but continues drawing. I tried on device too, and same problems happen but lesser in number. Also, the display sometimes flickers, or goes completely blank. When its completely black, it comes back if I touch once.
Is there anything wrong that I am doing, shall I change my approach.
Any suggestions are appreciated. Thanks
Upvotes: 1
Views: 417
Reputation: 20885
It's a synchronization problem. Basically, invalidate()
is not a blocking call: it just tells the system to redraw at some point in the future. So what happens:
invaldate()
schedules a redraw as soon as possibleYou can prove it simply by adding two hits counter, one for invalidate()
and one for onDraw()
. After some time you see that the number of calls to invalidate()
is greater than the hits for onDraw()
. My suggestion is to keep the points from touch events in a Queue
. Also note that every time you allocate a Bitmap
you need to call recycle
when you have done to avoid memory leaks. In fact, the pixels are stored in native memory and are not garbage collected when the view is destroyed. I usually call recycle
when my activity stops. Here is my code:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public static class Drawer extends View {
private Bitmap cache;
private Queue<PointF> points;
private PointF from;
public Drawer(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
points = new ConcurrentLinkedQueue<PointF>();
}
@Override
public boolean onTouchEvent(MotionEvent evt) {
switch (evt.getAction()) {
case MotionEvent.ACTION_DOWN: from = new PointF(evt.getX(), evt.getY()); break;
case MotionEvent.ACTION_MOVE: points.add(new PointF(evt.getX(), evt.getY())); invalidate(); break;
case MotionEvent.ACTION_UP: from = null; break;
default: from = null;
}
return true;
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w == 0 || h == 0)
return;
cache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
@Override
public void onDraw(Canvas systemCanvas) {
int w = getWidth();
int h = getHeight();
if (w == 0 || h == 0)
return;
if (cache == null)
cache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
// Draw on the cache
Canvas canvas = new Canvas(cache);
Paint paint = new Paint();
paint.setStrokeWidth(4);
paint.setColor(Color.MAGENTA);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
drawPoints(points, canvas, paint);
// Draw the cache with the system canvas
systemCanvas.drawBitmap(cache, 0, 0, paint);
}
private void drawPoints(Queue<PointF> points, Canvas canvas, Paint paint) {
if (from == null)
return;
PointF to;
while ((to = points.poll()) != null) {
canvas.drawLine(from.x, from.y, to.x, to.y, paint);
from = to;
}
}
}
}
and this is the layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="@+id/drawView">
<view
class="com.zybnet.test.MainActivity$Drawer"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
You can see that customs view can be used in XML, too :)
Upvotes: 2
Reputation: 1576
For motionEvent.ACTION_MOVE they can and will be batched. I believe this is why there are breaks in the lines.
See this from documentation:
Batching
For efficiency, motion events with ACTION_MOVE may batch together multiple movement samples within a single object. The most current pointer coordinates are available using getX(int) and getY(int). Earlier coordinates within the batch are accessed using getHistoricalX(int, int) and getHistoricalY(int, int). The coordinates are "historical" only insofar as they are older than the current coordinates in the batch; however, they are still distinct from any other coordinates reported in prior motion events. To process all coordinates in the batch in time order, first consume the historical coordinates then consume the current coordinates.
Upvotes: 0