I have a problem with simple drawing application on Android.
Here's my class which is extending SurfaceView:
public class SomeView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
static Canvas canvas=null;
static Paint paint=new Paint();
float X,Y,X1,Y1;
static int mode=1;
public static void ClearAll(){
canvas=surfaceHolder.lockCanvas(null);
canvas.drawColor(Color.BLACK);
surfaceHolder.unlockCanvasAndPost(canvas);
}
public SomeView(Context context,AttributeSet attrs) {
super(context);
getHolder().addCallback(this);
paint.setColor(Color.WHITE);
paint.setStyle(Style.FILL);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
this.surfaceHolder=holder;
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public boolean onTouchEvent(MotionEvent event)
{
canvas=surfaceHolder.lockCanvas(null);
if (mode==1){
canvas.drawCircle(event.getX(), event.getY(),10, paint);
}
if (mode==2){
paint.setStrokeWidth(10);
if (event.getAction()==MotionEvent.ACTION_DOWN){
X=event.getX();
Y=event.getY();
}
if (event.getAction()==MotionEvent.ACTION_UP){
X1=event.getX();
Y1=event.getY();
canvas.drawLine(X, Y, X1, Y1, paint);
}
}
surfaceHolder.unlockCanvasAndPost(canvas);
return true;
}
}
mode=1 - is simple user's touches tracing,
mode=2 - is straight-lines drawing
However, when I'm drawing in mode=2 the picture becomes weird:
Some lines disappear and after few more lines were drawn appears again. When I'm still touching the screen the canvas is blinking and showing all the lines were drawn since last ClearAll() call. If I stop touching only few lines are still visible.
What is the problem?
SurfaceView used double buffering. Generally - on each cycle/action you have to draw every pixel you want to be visible on the canvas.
Hope that helps.
Related
I'm trying to implement Pinch to Zoom to position. I'm working on it over a week now and I can't figure it out.
Here is my problem:
I created a custom view, extending SurfaceViewand implementing ScalingGestureDetector. Everything works fine, while I haven't scaled my canvas yet or pivot point are the same as in the previous scaling. When pivot point is different than in the previous scaling, canvas jumps to different x,y position and scaling to it not to finger focus point. I can't use matrix, because I draw text and rects, not bitmap.
Here's my code
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, ScaleGestureDetector.OnScaleGestureListener {
private ScaleGestureDetector mScaleDetector;
private float focusX, focusY;
private float scale = 1f;
private void init(Context context) {
mScaleDetector = new ScaleGestureDetector(context, this);
getHolder().addCallback(this);
// other init code
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
tryDrawing(holder);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int frmt, int w, int h) {
tryDrawing(holder);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {}
public void tryDrawing() {
tryDrawing(getHolder());
}
private void drawMyStuff(final Canvas canvas) {
Log.i(TAG, "Drawing...");
for(Pixel pixel : pixels) {
drawRect(canvas, pixel);
}
}
public void tryDrawing(SurfaceHolder holder) {
Log.i(TAG, "Trying to draw...");
Canvas canvas = holder.lockCanvas();
if (canvas == null) {
Log.e(TAG, "Cannot draw onto the canvas as it's null");
} else {
canvas.scale(scale, scale, focusX , focusY);
canvas.drawColor(Color.WHITE);
drawMyStuff(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
scale *= detector.getScaleFactor();
// Don't let the object get too small or too large.
scale = Math.max(1.0f, Math.min(scale, 5.0f));
tryDrawing();
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
focusX = detector.getFocusX();
focusY = detector.getFocusY();
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}`
If someone don't understand exactly what I mean, I can create some video later.
Thanks!
I had a problem like yours in one of my projects. I tried to think out of the box and instead of trying to give a pivot for the scale.
I used the default pivot (top-left corner..) but I translated the canvas to the pivot, so the top left corner will be at the position of your pivot. After that I translated my bitmaps and rects back to their proper place. Something like this might do it:
private void drawMyStuff(final Canvas canvas) {
Log.i(TAG, "Drawing...");
for(Pixel pixel : pixels) {
drawRect(canvas, pixel); // translate your rect with x : -focusX, y: -focusY
}
}
public void tryDrawing(SurfaceHolder holder) {
Log.i(TAG, "Trying to draw...");
Canvas canvas = holder.lockCanvas();
if (canvas == null) {
Log.e(TAG, "Cannot draw onto the canvas as it's null");
} else {
// Translate the canvas (so the top left corner will be in the wanted position and you can use it as a pivot)
canvas.translate(focusX , focusY);
canvas.scale(scale, scale); // Use the default 0,0 pivot
canvas.drawColor(Color.WHITE);
drawMyStuff(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
Hope it helps..
I'm trying to draw a bitmap into surfaceView I can successfully draw but I need to move that bitmap around the screen based on some other user movements but when I set
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(bitmap, left, top, null);
It draws same bitmap multiple times in screen.
But when I do this way
canvas.drawColor(Color.GREEN);
canvas.drawBitmap(bitmap, left, top, null);
It works correctly draws just one bitmap and moves it , but I need transparent background not colored.
CODE
public class DotsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private boolean created;
Bitmap bitmap;
#Override
public void surfaceCreated(SurfaceHolder holder) {
// draw();
created = true;
}
#Override
// This is always called at least once, after surfaceCreated
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// draw();
created = true;
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public DotsSurfaceView(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
holder.setFormat(PixelFormat.TRANSPARENT);
Drawable drawable = ARTrackingActivity.contexti.getDrawable(R.drawable.ic_tune_black_24px);
bitmap =Utils.drawableToBitmap(drawable);
}
public void draw(float left, float top) {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
synchronized (holder) {
draw2(canvas, left, top);
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
public void draw2(Canvas canvas, float left, float top) {
if (created) {
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(bitmap, left, top, null);
}
}
}
I have problems with my drawing application. I need to catch the correct touch screen and draw them to the canvas. Changing the size of the brush is working properly. But when I change the transparency setting, the program does not work correctly. It imposes a new path on top of the previous one, and the transparency is lost. Screenshot. Where could I go wrong? I need your help. Thank you.
This is my SurfaceView code:
public class PainterView extends SurfaceView implements SurfaceHolder.Callback {
private PainterThread painterThread;
private BrushParameters brushParameters;
private Bitmap bitmap;
public PainterView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
brushParameters = new BrushParameters();
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
setWillNotDraw(false);
getThread().setRunning(true);
getThread().start();
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
getThread().setBitmap(bitmap, true);
} else {
getThread().setBitmap(bitmap, false);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
getThread().setRunning(false);
boolean retry = true;
while (retry) {
try {
getThread().join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
painterThread = null;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
painterThread.startDraw(x, y);
break;
case MotionEvent.ACTION_MOVE:
painterThread.continueDraw(x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
painterThread.finishDraw(x, y);
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
}
public BrushParameters getBrushParameters() {
return brushParameters;
}
public void setBrushColor(int color) {
brushParameters.setColor(color);
getThread().setBrushParameters(brushParameters);
}
public void setBrushSize(int size) {
brushParameters.setSize(size);
getThread().setBrushParameters(brushParameters);
}
public void setBrushAlpha(int alpha) {
brushParameters.setAlpha(alpha);
getThread().setBrushParameters(brushParameters);
}
public PainterThread getThread() {
if (painterThread == null) {
painterThread = new PainterThread(getHolder(), this);
}
return painterThread;
}
}
And my Thread class:
public class PainterThread extends Thread {
private SurfaceHolder surfaceHolder;
private PainterView painterView;
private boolean running = false;
private Paint paint;
private Path path;
private Bitmap mBitmap;
private Canvas mCanvas;
private float lastX, lastY;
private static float TOUCH_TOLERANCE = 4;
public PainterThread(SurfaceHolder surfaceHolder, PainterView painterView) {
this.surfaceHolder = surfaceHolder;
this.painterView = painterView;
path = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(Color.BLACK);
paint.setAlpha(255);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
}
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
Canvas mCanvas;
while (running) {
mCanvas = null;
try {
mCanvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
if (mCanvas != null) {
mCanvas.drawBitmap(mBitmap, 0, 0, paint);
painterView.postInvalidate();
}
}
} finally {
if (mCanvas != null) {
surfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
public void setBitmap(Bitmap bitmap, boolean clear) {
mBitmap = bitmap;
if (clear) {
mBitmap.eraseColor(Color.WHITE);
}
mCanvas = new Canvas(mBitmap);
}
public void setBrushParameters(BrushParameters brushParameters) {
paint.setColor(brushParameters.getColor());
paint.setAlpha(brushParameters.getAlpha());
paint.setStrokeWidth(brushParameters.getSize());
}
public void startDraw(float x, float y) {
path.reset();
path.moveTo(x, y);
lastX = x;
lastY = y;
}
public void continueDraw(float x, float y) {
float dx = Math.abs(x - lastX);
float dy = Math.abs(y - lastY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(lastX, lastY, (x + lastX) / 2, (y + lastY) / 2);
mCanvas.drawPath(path, paint);
lastX = x;
lastY = y;
}
}
public void finishDraw(float x, float y) {
path.moveTo(x, y);
mCanvas.drawPath(path, paint);
}
}
Thanks for help. I lost fiew days for find a cause of the problem...
I recommend getting rid of SurfaceView. You've got some confused-looking code (e.g. setBitmap() sets mCanvas, but that's getting overwritten in a loop by run()), and I think you're just making life harder on yourself.
SurfaceViews have two parts, the Surface and the View. The Surface is a separate layer that (by default) sits behind the View layer. The View part of the SurfaceView is normally just a transparent hole that lets you "see through" to the Surface layer behind.
In your case, you've overridden onDraw() in the View object, so you're actually drawing in the View. Over in your other thread you're drawing that same Bitmap onto the Surface. Even if your Bitmap has transparent pixels, you'll be seeing two identical Bitmaps layered on top of each other.
It looks like you're sharing a Bitmap and possibly a Canvas between two simultaneously-executing threads, which is a recipe for unhappiness.
If you get rid of the SurfaceView, and just use a custom View, I think everything will make more sense. The other approach is to get rid of onDraw() and the call to postInvalidate() and do everything on the Surface, but to take advantage of hardware-accelerated rendering it's better to use the custom View.
I would like to add effects to my pointer. That is, while I am scrolling my finger in screen, show up effect or animation around which is showed up touched area. how to add?
I've found this:
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);
public DrawingView(Context context) {
super(context);
surfaceHolder = getHolder();
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
Canvas canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.BLACK);
canvas.drawCircle(event.getX(), event.getY(), 50, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
return false;
}
}
}
I am trying to understand using surfaceview. I want an image to move from left to right using surfaceview. Currently image is drawn but I am unable to make it move. Where should I call the variables x1 and y1. Also how could I call the invalidate() method, I want to make the image move from left to right repeatedly. Thanks
public class GameBoardTwo extends SurfaceView {
private Bitmap bitmap1;
private SurfaceHolder holder;
int x1, y1;
public GameBoardTwo(Context context) {
super(context);
holder = getHolder();
bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.blueball);
init();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = holder.lockCanvas(null);
drawSomething(c);
holder.unlockCanvasAndPost(c);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
}
private void init(){
x1 = 0;
y1 = 100;
}
protected void drawSomething(Canvas canvas) {
canvas.drawColor(Color.WHITE);
if(x1 < canvas.getWidth()){
x1 += 5;
}
else{
x1 = 0;
}
canvas.drawBitmap(bitmap1, x1, y1, null);
}
}
You need to use thread/runnable inteface and override run(). Something like this:
#Override
protected void run(){
while(true){
drawSomething(c);//c is your locked canvas
try{
Thread.sleep(50)
} catch(Exception e){}
}
}