SurfaceView unnecessary redrawings - android

I need to write finger paint application. I need some computations about the drawn curve, so I think it's a good idea to use SurfaceView but not View.
I've written code for drawing. I collect the points into Path and draw it. On ACTION_UP this Path is reseting. And in the drawing loop I draw only the last Path.
But for some reason previous paths are redrawn too. This cause annoying blinking, which I don't want. I've changing line color on every draw cycle and I see, that previous paths change their colors, despite of there is no code to collect and redraw them.
Why so? Is there any way to make SurfaceView not to repeat all previous drawings?
Here is my code:
public class OtherDrawView extends SurfaceView implements SurfaceHolder.Callback {
private DrawingThread mThread;
private Path mPath;
private static final Object lock = new Object();
public OtherDrawView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
mThread = new DrawingThread(surfaceHolder);
mPath = new Path();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(), event.getY());
continueDrawingThread();
return true;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
continueDrawingThread();
return true;
case MotionEvent.ACTION_UP:
mPath.reset();
return true;
}
return false;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
continueDrawingThread();
while (retry) {
try {
mThread.join();
retry = false;
}
catch (InterruptedException ignored) { }
}
}
private void continueDrawingThread() {
synchronized (lock) {
try {
lock.notifyAll();
} catch (Exception ignored) {}
}
}
private class DrawingThread extends Thread {
private boolean mRunning;
private Paint mPaint;
private Random mRandom = new Random(System.currentTimeMillis());
private final SurfaceHolder mSurfaceHolder;
public DrawingThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
}
public void setRunning(boolean running) {
mRunning = running;
}
private void drawPath() {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
assert canvas != null;
mPaint.setColor(Color.rgb(mRandom.nextInt(255), mRandom.nextInt(255), mRandom.nextInt(255)));
canvas.drawPath(mPath, mPaint);
}
}
catch (Exception ignored) { }
finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void run() {
while (mRunning) {
drawPath();
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException ignored) {
}
}
}
}
}
}

I still can't understand why SurfaceView behaves like this, but I've found a workaround: draw not directly on canvas but on some bitmap and then output this bitmap on main canvas. Fully described in this article: http://android-coding.blogspot.ru/2012/01/flickering-problems-due-to-double.html

From the documentation for unlockCanvasAndPost(Canvas):
"After this call, the surface's current pixels will be shown on the screen, but its content is lost, in particular there is no guarantee that the content of the Surface will remain unchanged when lockCanvas() is called again."
So, when drawing to a surface view, you always have to clear the background (normally by re-drawing a background image) before drawing anything else on top, eg. the path in your case.

Related

Pause and resume threads in Android

I am beginner to multithreading in Java and in my Android application, i have that SurfaceView on wich am drawing a circle randomlly but i want to be able to pause that drawing by pressing the screen (ACTION_DOWN) and resume it the next time i press it again :
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class GameView extends SurfaceView /**/implements SurfaceHolder.Callback {
private float x = 100;
private float y = 100;
private int radius = 20;
private Paint paint;
private SurfaceHolder mSurfaceHolder;
private DrawingThread mThread;
private Context myContext;
public GameView(Context context) {
super(context);
this.myContext = context;
setWillNotDraw(false);
paint = new Paint();
/**/
paint.setAntiAlias(true);
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setTextAlign(Paint.Align.LEFT);
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
//paint.setTextSize(15);
}
public void onDraw(Canvas canvas){
canvas.drawCircle(x, y, radius, paint);
}
/**/
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread = new DrawingThread(mSurfaceHolder, myContext);
mThread.mRun = true;
mThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
public synchronized boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction ) {
case MotionEvent.ACTION_DOWN:
this.mThread.canPause = !this.mThread.canPause;
synchronized(this.mSurfaceHolder){
if(this.mThread.canPause){
this.mSurfaceHolder.notify();
}
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate();
return true;
}
public final class DrawingThread extends Thread {
public boolean canPause = false;
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
public DrawingThread(SurfaceHolder sholder, Context ctx)
{
surfaceHolder = sholder;
context = ctx;
mRun = false;
}
void setRunning(boolean bRun)
{
mRun = bRun;
}
boolean keepDrawing = true;
#Override
public void run() {
while (keepDrawing) {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
while(canPause){
try {
mSurfaceHolder.wait();
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
waitThreaed();
draw(canvas);
}
}
catch(Exception e){
}
finally {
if (canvas != null)
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
public void waitThreaed() {
try {
x = (float) (getWidth()*Math.random());
y = (float) (getHeight()*Math.random());
this.sleep(1000);
postInvalidate();
} catch (InterruptedException e) {
}
}
}
}
In fact with that code the drawing can be paused but can not be resumed
In order to call wait() you must have synchronized on the object you are waiting on.
See this question for a discussion of that: Why must wait() always be in synchronized block

Pausing and resuming thread in SurfaceView in Android

I have a SurfaceView on wich am drawing a small circle periodically with a special thread
but i want to when i press the surfaceView the thread stops drawing the circle and when i repress it the thread resume drawing the circle again.
I tried with thread methods and i can stop temporarily it with its sleep() method but i did not understand how to use wait and notify and i even found some exemples but did not get help from them
My code is :
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private float x = 100;
private float y = 100;
private int radius = 20;
private Paint paint;
private SurfaceHolder mSurfaceHolder;
private DrawingThread mTh ead;
private Context myContext;
public GameView(Context context) {
super(context);
this.myContext = context;
setWillNotDraw(false);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setTextAlign(Paint.Align.LEFT);
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
}
public void onDraw(Canvas canvas){
canvas.drawCircle(x, y, radius, paint);
}
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction ) {
case MotionEvent.ACTION_DOWN:
// I want to do my job here
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate();
return true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread = new DrawingThread(mSurfaceHolder, myContext);
mThread.mRun = true;
mThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public final class DrawingThread extends Thread {
public boolean att = true;
public long delaiAttente = 1000;
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
public DrawingThread(SurfaceHolder sholder, Context ctx)
{
surfaceHolder = sholder;
context = ctx;
mRun = false;
}
void setRunning(boolean bRun)
{
mRun = bRun;
}
boolean keepDrawing = true;
#Override
public void run() {
while (keepDrawing) {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
draw(canvas);
}
}
catch(Exception e){
}
finally {
if (canvas != null)
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
waitThreaed();
}
}
public void waitThreaed() {
try {
x = (float) (getWidth()*Math.random());
y = (float) (getHeight()*Math.random());
this.sleep(1000);
postInvalidate();
} catch (InterruptedException e) {
}
}
}
}
Can't you just use something like this:
Timer drawTimer = new Timer("draw");
updateTimer.schedule(new TimerTask() {
public void run() {
draw();
}
}, 0, 1000);
private void draw() {
runOnUiThread(new Runnable() {
public void run() { ...}}}
I don't see why you need to subclass Thread.

canvas zoom in partially

I want to zoom in the canvas board partially as the letter tray button will be fixed and the board will be zoom,
The partially coding is as mention below and the screen is fully draw in canvas which can be viewed as mention at the bottom.. please help and thanks for you concern.
public class BoardView extends SurfaceView implements SurfaceHolder.Callback
{
class DrawingThread extends Thread implements OnTouchListener {
public DrawingThread(SurfaceHolder holder, Handler handler) {
mSurfaceHolder = holder;
public void setRunning(boolean b) {
mRun = b;
}
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
// System.gc();
// c.scale(canvasScaleX, canvasScaleY);
// c.save();
// c.translate(canvasTranslateX, canvasTranslateY);
doDraw(c);
}
updateGame();
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
private void doDraw(Canvas canvas) {
if (ge == null)
return;
Rect bRect = new Rect(0, 0, dims.getTotalWidth(),
dims.getScoreHeight() + dims.getBoardheight());
Drawable drawable = getResources().getDrawable(R.drawable.board);
drawable.setBounds(bRect);
drawable.draw(canvas);
Rect tRect = new Rect(0, dims.getScoreHeight()
+ dims.getBoardheight(), dims.getTotalWidth(),
dims.getTotalHeight());
canvas.drawRect(tRect, fillTrayPaint);
int topHeight = dims.getScoreHeight() + dims.getBoardheight();
int bottom = (dims.getTotalHeight() + 5)
- (dims.getTotalWidth() / Tray.TRAY_SIZE);
Rect rect = new Rect(0, topHeight, dims.getTotalWidth(), bottom - 7);
Drawable drawableTray = getResources()
.getDrawable(R.drawable.strip);
drawableTray.setBounds(rect);
drawableTray.draw(canvas);
drawTray(canvas);
drawBoard(canvas);
// drawScore(canvas);
drawMovingTile(canvas);
}
public BoardView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// endTurn=(ImageButton)findViewById(R.id.endturn_button_horizontal);
thread = new DrawingThread(holder, handler);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
final float scale1 = getResources().getDisplayMetrics().density;
defaultFontSize = (int) (MIN_FONT_DIPS * scale1 + 0.5f);
thread.setDefaultFontSize(defaultFontSize);
dimensions = calculateDimensions(getWidth(), getHeight());
thread.setDimensions(dimensions);
thread.setRunning(true);
// if (thread != null && !thread.isAlive())
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
We can simply do it by canvas.clipRect(rectangle), at this point what we are suppose to do is save the canvas canvas.save() and after canvas.clipRect(rectangle) call canvas.restore().
Consider trying something like this:
Create a bitmap from the canvas you wish to zoom into.
When rendering that bitmap to the screen, you can change the scr rect
(which will "zoom" into the bitmap).
I hope this helps.
I think you need to render your game board into offscreen bitmap and then draw this bitmap at canvas using Matrix, so you will be able to change its position and scale.
I believe the easiest way to do this would be to use Canvas#scale(float scale, int pivotx, int pivoty) method. It essentially grows the entire canvas uniformally which really is what zooming is from a 2D perspective.
canvas.scale(2.0f, getWidth()/2, getHeight()/2) will scale the image by 2 from the center. canvas.scale(1.0f, getWidth()/2, getHeight()/2) will shrink it back.
I have never tried this in this context, so if you decide to try it report back the results please! I'd like to know for future reference.

android draw over image

I am creating a paint app but i am not able to draw /paint anything over an image please help me out :
public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {
private Boolean _run;
protected DrawThread thread;
private Bitmap mBitmap;
public boolean isDrawing = true;
public DrawingPath previewPath;
private CommandManager commandManager;
public DrawingSurface(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
commandManager = new CommandManager();
thread = new DrawThread(getHolder());
}
private Handler previewDoneHandler = new Handler(){
#Override
public void handleMessage(Message msg) {
isDrawing = false;
}
};
class DrawThread extends Thread{
private SurfaceHolder mSurfaceHolder;
public DrawThread(SurfaceHolder surfaceHolder){
mSurfaceHolder = surfaceHolder;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas canvas = null;
while (_run){
if(isDrawing == true){
try{
canvas = mSurfaceHolder.lockCanvas(null);
if(mBitmap == null){
// mBitmap = Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);
mBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.splashscreen);
}
// final Canvas c = new Canvas (mBitmap);
mBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.splashscreen);
// c.drawColor(0, PorterDuff.Mode.CLEAR);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
// commandManager.executeAll(c,previewDoneHandler);
// previewPath.draw(c);
canvas.drawBitmap (mBitmap, 0, 0,null);
} finally {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public void addDrawingPath (DrawingPath drawingPath){
commandManager.addCommand(drawingPath);
}
// Bitmap mBitmap = Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter);
public boolean hasMoreRedo(){
return commandManager.hasMoreRedo();
}
public void redo(){
isDrawing = true;
commandManager.redo();
}
public void undo(){
isDrawing = true;
commandManager.undo();
}
public boolean hasMoreUndo(){
return commandManager.hasMoreUndo();
}
public Bitmap getBitmap(){
return mBitmap;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
}
Why not make it easier on yourself and split your application across multiple surfaceviews. Use framelayout so that you can control the order of the views. I assume you want to be able to handle touch events (its a paint program right?) so handle those in one view and your drawing in another that way the code will be more manageable. Create two custom surfaceviews (In my case one was a GLSurfaceView, and the other was a SurfaceView). One view implements the image and the second can handle a GUI with graphics and or buttons. The key is creating a custom GLSurfaceView (or in your case two Surfaceviews) in XML that uses the class names of the extended SurfaceViews. Also you must make the GUI view transparent. So your Surface view that calls your drawing class and handles touch events would have code like this:
public class MainSurfaceView extends GLSurfaceView
//Constructor
public MainSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "Constructor called...");
this.context = context;
mRenderer = new MainRenderer();
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
setRenderer(mRenderer);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
//Render only when there is a change
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
//TouchEvent handler
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent called...");
x = event.getX();
y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mPreviousX;
float dy = y - mPreviousY;
//Reverse direction of rotation if above midline
if (y > getHeight() / 2) {
dx = dx * -1;
}
//Reverse direction of rotation if of the midline
if (y < getWidth() / 2) {
dy = dy * -1;
}
Main.mAngle += (dx + dy) * TOUCH_SCALE_FACTOR;
TextView txtView = (TextView) ((Activity)context).findViewById(R.id.mangle);
txtView.setText("Angle: " + String.valueOf(Main.mAngle));
requestRender();
}
mPreviousX = x;
mPreviousY = y;
return true;
}
Put you image to be drawn on a separate surface view (it will also make it easier to change the image). So you Activity class would have code like this (note my second view is a GLsurfaceview):
public class Main extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate called...");
Log.d(TAG, "state is: " + state.toString());
super.onCreate(savedInstanceState);
// When working with the camera, it's useful to stick to one orientation.
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//Set full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
//Substantiate surfaces
cameraView = (CameraView) findViewById(R.id.camera);
mGLView = (MainSurfaceView) findViewById(R.id.glSurfaceView);
//Initialize TextViews
txtView = (TextView) findViewById(R.id.mangle);
txtView.setText("Angle: " + String.valueOf(mAngle));
}
}
Hope that helps a little...

Android SurfaceView shows black screen

I am trying to draw on a surface view, but I get a black screen. My xml layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<com.example.surfaceview.MySurface android:id="#+id/padview"
android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
Important parts of my code: MySurface.java
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
public DrawingThread thread;
public MySurface(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder surfaceholder = getHolder();
surfaceholder.addCallback(this);
thread = new DrawingThread(surfaceholder, context, new Handler() {
#Override
public void handleMessage(Message m) {
// Do some handle thing
}
});
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder tholder, int format, int width, int height) {
thread.setSurfaceSize(width, height);
thread.holder = tholder;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.running = true;
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.running = false;
while(retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
public Thread getThread() {
return thread;
}
public class DrawingThread extends Thread {
private SurfaceHolder holder;
private Context context;
private Handler handle;
private Paint background;
private Rect blank;
public boolean running;
public boolean created;
public int canvasheight;
public int canvaswidth;
public PadThread(SurfaceHolder tholder, Context tcontext, Handler thandler) {
holder = tholder;
context = tcontext;
handle = thandler;
// Temporary canvas dimentions
canvaswidth = 1;
canvasheight = 1;
running = false;
created = false;
background = new Paint();
background.setColor(R.color.white);
blank = new Rect(0, 0, canvaswidth, canvasheight);
}
#Override
public void run() {
Log.d("SurfaceView Test", "Drawing thread run");
while(running) {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
synchronized(holder) {
// update object states
// get user input gestures
drawing(canvas);
}
} finally {
if(canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
private void drawing(Canvas canvas) {
// Clear screen
canvas.drawRect(blank, background);
// Draw Things
}
public void setSurfaceSize(int width, int height) {
synchronized(holder) {
canvaswidth = width;
canvasheight = height;
// New background rect
blank.set(0, 0, canvaswidth, canvasheight);
}
}
}
}
The code is based off of Google's Lunar Landar SurfaceView example at http://developer.android.com/resources/samples/LunarLander/index.html
I know that all of the code is being reached through logging.
change drawing function
background.setColor(Color.RED);
canvas.drawRect(new Rect(10, 10, 100, 100), background);
(There are test values for colors and rect)

Categories

Resources