why is invalidate not working? - android

I am trying to draw a rectangle where I am initially providing it values from Activity class and then trying to change its coordinates from a different class.. As far as I am concerned the logic is right, but the invalidate is not working.. I did substantial research but still cannot make it work..
RectangleActivity.java
public class RectangleActivity extends Activity {
private Point dStart = null;
private Point dEnd = null;
final private Handler handler = new Handler();
Check check = new Check(this);
Draw draw ;
Runnable mUpdate = new Runnable()
{
public void run()
{
Toast.makeText(getApplicationContext(), "update Called", Toast.LENGTH_LONG).show();
check.update();
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dStart = new Point(0,0);
dEnd = new Point(60,60);
draw = new Draw(this,dStart,dEnd);
setContentView(draw);
handler.postDelayed(mUpdate, 4000);
}
}
Check.java
public class Check
{
private Point start = new Point(80,80);
private Point end = new Point(150,150);
private Context context;
public Check(Context context)
{
this.context =context;
}
void update()
{
Log.d("Aditi", "Update Called");
Draw draw = new Draw(context);
draw.reDraw(start,end);
}
}
Draw.java
public class Draw extends View
{
private Paint paint = null;
private Point start = null;
private Point end = null;
public Draw(Context context)
{
super(context);
paint();
}
public Draw(Context context, Point start, Point end)
{
super(context);
this.start= start;
this.end = end;
paint();
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawRect(start.x,start.y,end.x,end.y,paint);
Log.d("Aditi", "OnDraw Called");
System.out.println("StartX in ondraw is: "+start.x);
System.out.println("StartY in ondraw is: "+start.y);
System.out.println("EndX in ondraw is: "+end.x);
System.out.println("EndY in ondraw is: "+end.y);
}
public void reDraw(Point Start, Point End)
{
Log.d("Aditi", "Redraw Called");
this.start = Start;
this.end = End;
invalidate();
Log.d("Aditi", "Invalidate Called");
System.out.println("StartX in redraw is: "+start.x);
System.out.println("StartY in redraw is: "+start.y);
System.out.println("EndX in redraw is: "+end.x);
System.out.println("EndY in redraw is: "+end.y);
}
public void paint()
{
paint= new Paint();
paint.setColor(Color.YELLOW);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
}
}
please feel free to point me out what am I doing wrong.. Thanks in advance..

I afraid if it do not work for you. Please try call the invalidate method onDraw()
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
invalidate();
}

In above problem, in onCreate() you took a view using draw = new Draw(this,dStart,dEnd);
and then used in setContentView(draw), but when update() called you again created an object of Draw by using Draw draw = new Draw(this,dStart,dEnd); and call draw.reDraw(start, end) on second draw object, so the instance used in setContentView() is not invalidated, And second draw view object get invalidated but it did not passed in setContentView(), so it is not visible on ui: your solution is just
void update() {
// Draw draw = new Draw(context); //remove this
draw.reDraw(start, end);
}

if you invalidate a view from a thread other than the app's main thread it will not work , you must call postInvalidate() instead of invalidate().

Related

android updating view from a thread. Only the original thread that created the view can touch its views.

I know this may seem like a noob question but I've tried everything. I want to animate a number incrementing. So I set up a view. I made a thread inside the view that loops a certain amount of times. In the loop I call Thread.sleep and then I increment a number. However I'm not sure how to call the invalidate() method to update the view each time it goes through the loop. I've tried a callback but that isn't working. Your help would be appreciated. What I'm trying to increment is the 'centerText' which is drawn using textPaint
public class GameView extends View {
private int backgroundColor;
private int circleColor;
private int radiusFactor;
private Paint circlePaint;
private Paint textPaint;
private Paint text2Paint;
private Paint text3Paint;
private String topMessage;
private String bottomMessage;
private String topMessage2;
private String bottomMessage2;
private String centerText;
private boolean isRunning = false;
private int FPS = 60;
public interface animateThread{
public void updateUI(String text);
}
private animateThread threadCheck = new animateThread(){
#Override
public void updateUI(String text) {
centerText="text";
invalidate();
}
};
public GameView(Context context) {
super(context);
backgroundColor = Color.parseColor("#FF4000");
circleColor = Color.parseColor("#B40404");
radiusFactor = 4;
centerText="?";
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setColor(circleColor);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
text2Paint = new Paint();
text2Paint.setAntiAlias(true);
text2Paint.setColor(Color.BLACK);
text2Paint.setTextAlign(Paint.Align.CENTER);
text3Paint = new Paint();
text3Paint.setAntiAlias(true);
text3Paint.setColor(Color.BLACK);
text3Paint.setTextAlign(Paint.Align.CENTER);
topMessage = "One in a";
topMessage2="Hundred?";
bottomMessage="Click the Circle";
bottomMessage2="To find out";
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
break;
}
if(!isRunning) startAnimation(threadCheck);
return true;
}
#Override
protected void onDraw(Canvas canvas) {
setBackgroundColor(backgroundColor);
textPaint.setTextSize(getWidth()/4);
text2Paint.setTextSize(getWidth()/6);
text3Paint.setTextSize(getWidth()/8);
canvas.drawCircle(getWidth()/2, getHeight()/2,getWidth()/radiusFactor,
circlePaint);
canvas.drawText(centerText, getWidth()/2, getHeight()/2-
((textPaint.descent()+textPaint.ascent())/2), textPaint);
canvas.drawText(topMessage, getWidth()/2, ((getHeight()/2-
getWidth()/radiusFactor)/2+((text2Paint.descent()+text2Paint.ascent())/2)),
text2Paint);
canvas.drawText(topMessage2, getWidth()/2, (getHeight()/2-
getWidth()/radiusFactor)/2-((text2Paint.descent()+text2Paint.ascent())),
text2Paint);
canvas.drawText(bottomMessage, getWidth()/2, getHeight()*4/5+
((text3Paint.descent()+text3Paint.ascent())/2), text3Paint);
canvas.drawText(bottomMessage2, getWidth()/2, getHeight()*4/5-
((text3Paint.descent()+text3Paint.ascent())), text3Paint);
}
public void startAnimation(final animateThread mCallback) {
Thread animate = new Thread(new Runnable(){
#Override
public void run() {
isRunning=true;
int count=0;
for (int i=0;i<FPS*5;i++) {
count++;
if(count>100) count = 1;
mCallback.updateUI(count+"");
try {
Thread.sleep(1000/FPS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isRunning=false;
}
});
animate.start();
}
}
Anything invoked within
new Thread(new Runnable(){
...
}
Will implicitly run on a non-UI thread. If you want to touch the UI, the code for that task must be forced to run on the UI thread like this:
context.runOnUiThread(new Runnable() {
#Override
public void run() {
updateUI(count+"");
}
});

Android invalidate doesn't show intermediate animation

This is my code.
On swipe to the top is performed:
for(int it = 0;it<4;it++){
myBoard.step_up();
invalidate();
}
int ad = myBoard.merge_up();
invalidate();
for(int it = 0;it<4;it++){
myBoard.step_up();
invalidate();
}
step_up and merge_up methods change the values of myBoard. The onDraw method draws the screen according to the myBoard values. But i do not see intermediate results of movements! Just start postition and then end position, the same as in the following code:
for(int it = 0;it<4;it++){
myBoard.step_up();
}
int ad = myBoard.merge_up();
for(int it = 0;it<4;it++){
myBoard.step_up();
}
invalidate();
How to deal this problem? It is very important for me
Update:
The code from onCreate:
GraphicsView myview=new GraphicsView(this);
setContentView(myview);
GraphicsView entire code is too big. The part of it:
public GraphicsView(Context context) { super(context);
this.setOnTouchListener(new OnSwipeTouchListener(getApplicationContext()) {
public void onSwipeTop() {
//above code
}
//other methods
}
protected void onDraw(Canvas canvas)
{
//draw
}
}

Custom SurfaceView with thread crashes App when Activity is exited

I just made an Activity which uses at "setContentView" a view from a class which extends SurfaceView. The problem is:
It works fine, but when I exit it (BACK key) it crashes. Code:
package ro.etrandafir.mate.appCreator;
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
import android.view.SurfaceView;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.graphics.Color;
import android.graphics.Paint;
public class Sample2 extends Activity implements View.OnTouchListener {
float x = 0, y = 0;
SampleTwoView theView;
public boolean onTouch(View v, MotionEvent event) {
// TODO: Implement this method
x = event.getX();
y = event.getY();
return true;
}
#Override
protected void onPause() {
super.onPause();
finish();
}
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
theView = new SampleTwoView(this);
theView.setOnTouchListener(this);
setContentView(theView);
}
public class SampleTwoView extends SurfaceView implements Runnable {
Paint p = new Paint();
public SampleTwoView(Context context) {
super(context);
p.setColor(Color.RED);
Thread theThread = new Thread(this);
theThread.start();
}
public void run() {
while (true) {
if (!getHolder().getSurface().isValid()) continue;
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
getHolder().unlockCanvasAndPost(canvas);
}
}
}
}
What can I do? Should I add onDestroy or what?
Thanks in advance,
Matei
The issue you are getting is related to this code:
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
When your activity ends, your thread is still running, but your custom SurfaceView is no longer available, so you will get a null ptr exception. Your existing code can easily be patched by adding a boolean that gets set to false as soon as the onPause fn gets called:
public void run() {
while (booleanThatGetsSetToFalseWhenActivityPauses) {
if (!getHolder().getSurface().isValid()) continue;
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
getHolder().unlockCanvasAndPost(canvas);
}
}
However, I would suggest altering the structure of your application as a whole. This may just be for practice, but I think a more efficient and bug free way of accomplishing your goal would be to simply use a standard SurfaceView and to completely decouple your drawing logic from any custom view.
My redesigned activity is below, but it utilizes a Ball class that is used to maintain the ball's logic, which, in your current code is separately coupled with both the actvity (the coordinates) and the view (the Paint). In this new ball class, a ball has a location (specified by a PointF), a Paint, and a diameter. It also has methods to get most of these variables in addition to setting some.
public class Ball {
private Paint mPaint;
private PointF mCoordinates;
private int mDiameter;
public Ball (int color, int diameter) {
mPaint = new Paint();
mPaint.setColor(color);
mCoordinates = new PointF();
mCoordinates.x = 0;
mCoordinates.y = 0;
mDiameter = diameter;
}
public void setCoordinates (float x, float y) {
mCoordinates.x = x;
mCoordinates.y = y;
}
public PointF getCoordinates() {
return mCoordinates;
}
public Paint getPaint() {
return mPaint;
}
public int getDiameter() {
return mDiameter;
}
/* You did not want to draw the uninitialized ball, so this method checks that */
public boolean hasNonZeroLocation () {
return (mCoordinates.x != 0 && mCoordinates.y != 0);
}
}
I use the Ball class in the activity as shown below. Notice that the redrawing to the canvas now only occurs when a user touches the canvas as opposed to an infinite while loop. This is due to the utilization of the Handler class which posts actions to run to the UI thread. Additionally, now we do not need a custom view, and our ball's logic has been decoupled from the activity and the view.
public class RedBallActivity extends Activity {
Handler mDrawingHandler;
SurfaceView mDrawingSurfaceView;
Ball mBall;
private final Runnable drawRedBallOnBlueSurface = new Runnable() {
#Override
public void run() {
if (!mDrawingSurfaceView.getHolder().getSurface().isValid()) return;
Canvas canvas = mDrawingSurfaceView.getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if (mBall.hasNonZeroLocation())
canvas.drawCircle(mBall.getCoordinates().x, mBall.getCoordinates().y, mBall.getDiameter(), mBall.getPaint());
mDrawingSurfaceView.getHolder().unlockCanvasAndPost(canvas);
}
};
private final OnTouchListener mCanvasTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mBall.setCoordinates(event.getX(), event.getY());
mDrawingHandler.post(drawRedBallOnBlueSurface);
return true;
}
};
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
mDrawingSurfaceView = new SurfaceView(this);
mDrawingSurfaceView.setOnTouchListener(mCanvasTouchListener);
setContentView(mDrawingSurfaceView);
mBall = new Ball(Color.RED, 40);
mDrawingHandler = new Handler();
}
}
Now, if you actually run this code you will notice that initially the screen is not drawn with a blue background. You might be tempted to simply call mDrawingHandler.post(drawRedBallOnBlueSurface); at the end of the onCreate method, but it is not guaranteed that the SurfaceView will be ready to be drawn upon (see the documentation on this lockCanvas method). If you want the surface to initially be blue, you need to implement a [SurfaceHolder.Callback][2], which needs to be connected to the SurfaceView's SurfaceHolder, and on the surfaceCreated method being called, we know the surface is ready, so we can then call mDrawingHandler.post(drawRedBallOnBlueSurface);
Now, with this added, I change the Activity to implement [SurfaceHolder.Callback][2] as follows:
public class FriendManagerActivity extends Activity implements SurfaceHolder.Callback {
and add this line to the constructor:
mDrawingSurfaceView.getHolder().addCallback(this);
and implement the interface:
#Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawingHandler.post(drawRedBallOnBlueSurface);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
Feel free to ask any questions on my little redesign! While your problem could be easily patched, I felt like the way you were coupling logic with Views was a little bit flawed, and thought a little more info on SurfaceView coding would be helpful.
As someone mentioned it in the above, when your activity ends, your thread is still running, but your custom SurfaceView is no longer available, so you will get a Null Point Exception. Your existing code can easily be patched by adding a boolean that gets set to false as soon as the onPause fn gets called:I had the same problem. To solve it I added the following onPause() to your SampleTwoView class:
// pause method will destroy the Thread
public void pause() {
isRunning = false;
while (true) {
try {
myThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
myThread = null;
}
Then call this onPause() method in your onPause() method of your Sample2 class as follows:
#Override
protected void onPause() {
super.onPause();
SampleTwoView.onPause();
finish();
}
So everytime the onPause() method of your main Activity class is called the Thread will be destroyed.
I hope this will help you.
Cheers!

Multi-threading in the same Canvas

Well, I'll try to explain exactly what I pretend:
I want to draw a bitmap in a canvas in random positions according to the coordinates of the screen on touch, and I want a message(text) to be displayed everytime the user touches the screen.
So, if a draw the text in the same thread than I do for the bitmap, it will appear and dissapear right after, and I want it to stay in the screen for a few seconds and the dissapear. My first idea was to use Thread.sleep(), but for that I have to create a thread only for the text, or I will mess with the Bitmap too.
I've been trying to use multithreading in the same canvas, but I don't know how. Can someone please explain to me...
That's some of the code i've got so far:
private void init() {
// CREATE SURFACEHOLDER AND ADD THIS CLASS AS HIS CALLBACK
enemyHolder = getHolder();
enemyHolder.addCallback(this);
scoreHolder = getHolder();
scoreHolder.addCallback(this);
hasSurface = false;
}
public void resume() {
if (surfaceViewThread == null) {
surfaceViewThread = new SurfaceViewThread(); // CREATE A NEW
// THREAD
if (hasSurface)
surfaceViewThread.start(); // START OUR THREAD
}
if (secondThread == null) {
secondThread = new SecondThread();
if (hasSurface)
secondThread.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
hasSurface = true;
if (surfaceViewThread != null)
surfaceViewThread.start();
if (scoreShow == 1) {
if (secondThread != null)
secondThread.start();
}
}
// THREAD
private final class SurfaceViewThread extends Thread {
private boolean done;
SurfaceViewThread() {
super();
done = false;
}
#Override
public void run() {
// TODO Auto-generated method stub
super.run();
SurfaceHolder surfaceHolder = enemyHolder;
while (!done) {
Canvas canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(enemy1, enemy1X, enemy1Y, null); // DRAW
// FIRST
// ENEMY
// SECOND THREAD
private final class SecondThread extends Thread {
private boolean done;
SecondThread() {
super();
done = false;
}
#Override
public void run() {
// TODO Auto-generated method stub
super.run();
SurfaceHolder surfaceHolder = scoreHolder;
while (!done) {
Canvas canvas = surfaceHolder.lockCanvas();
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawText("xD", 50, 50, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
scoreShow = 0;
}
}
Do you need to use a SurfaceView? It seems like a hard work way of doing things.
I've just done something similar by creating a custom view class and overriding the onDraw method. Then use canvas.save() and canvas.restore(). Here's the relevant bits of my onDraw.
#Override
public void onDraw(Canvas canvas) {
canvas.save();
// scale the canvas
canvas.scale(scaleFactor, scaleFactor); //, mid.x, mid.y);
// and translate...
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
super.onDraw(canvas);
// draw the lights
for(Light light:lights){
if (light.isOn){
canvas.drawCircle(light.getX(),light.getY(), light.getDiameter() / scaleFactor,light.paint);
}
}
canvas.restore();
}
The lights are turned on and off by a separate thread back in the activity which has inflated the view. They stay on screen as long as they are on.
Cheers
Here's some incomplete pseudo code to do what you've described. There are plenty of good examples around for each of these elements to help you fill in the blanks.
public class MyCustomView extends View {
int touchPointX;
int touchPointY;
private String mText;
public MyCustomView(Context context) {
// do initialisation stuff
}
public setText(String text){
mText = text;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
canvas.save();
super.onDraw(canvas);
canvas.drawBitmap(touchPointX, touchPointY, bitmap)
if (!mText.equals(""){
canvas.drawText(touchPointX, touchPointY + 10, mText)
}
canvas.restore();
}
}
public class MyActivity extends Activity implements OnTouchListener{
private Handler mHandler = new Handler();
private MyCustomView mCustomView;
#Override
protected void onCreate(Bundle savedInstanceState) {
mCustomView = (MyCustomView) findViewById(r.id.myCustomView);
}
#Override
public boolean onTouch(View v, MotionEvent rawEvent) {
// get x and y of touch
mCustomview.touchPointX = x;
mCustomview.touchPointY = y;
mCustomView.setText("Screen touched");
// clear the text after 5 seconds
mHandler.postDelayed(clearText, 5000);
// redraw the view
mCustomView.invalidate();
}
private void clearText(){mCustomView.setText("");}
}

Seamless background as custom view component , onDraw is not get called by invalidate()

Friends,
i want to make a seamless background as a custom view component.
the problem i figured out during debugging,
the first time i call invalidate() within the thread my onDraw callback method is called.
The other times my thread is calling invalidate() the onDraw callback method is not called.
so its just running over the invalidate() method , like it would not even exist.
The application displays the seamlessbackground png. But as static. it not gets updated.
I post all my code because the bug might be outside of the thread, where invalidate() is located or outside the onDraw method.
apprechate any kind of help! so THX
public class MyBringBackSurface extends View implements Runnable {
Bitmap bitmapResource;
int leftFrameX, rightFrameX, startX, stopX, bitmapPixelWidth,
bitmapPixelHight;
int pixelPerFrame = 10;
int framerate = 100;
int threadSleepTime;
int offsetYInPixel = 200;
Thread ourthread = null;
boolean isrunning = false;
Canvas c;
public MyBringBackSurface(Context context) {
super(context);
init();
}
public MyBringBackSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyBringBackSurface(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// Set up Bitmap Resource
bitmapResource = BitmapFactory.decodeResource(getResources(),
R.drawable.simelessbackground);
// c = new Canvas(bitmapResource);
// Implementing leftFrameX and rightFrameX
leftFrameX = 0;
rightFrameX = bitmapResource.getWidth();
// Calculating the Thread sleep time
threadSleepTime = 1000 / framerate;
}
public void pause() { // destroy the currently running thread because
// anyways in the on resume will be created a
// new one again
isrunning = false;
while (true) {
try { // goes through this thread until our thread died
ourthread.join(); // Blocks the current Thread
// (Thread.currentThread()) until the
// receiver finishes its execution and
// dies.
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
public void resume() {
isrunning = true;
ourthread = new Thread(this);
ourthread.start();
}
public void run() {
while (isrunning) {
try {
Thread.sleep(threadSleepTime);
// formula is 1000/ sleep time (here 5) = frame rate
} catch (InterruptedException e) {
e.printStackTrace();
}
invalidate();
// Add pixelPerFrame and draw again
leftFrameX = leftFrameX - pixelPerFrame;
rightFrameX = rightFrameX - pixelPerFrame;
// if picture is completely out of the screen, start over again
if (leftFrameX <= -bitmapResource.getWidth()) {
leftFrameX = 0;
rightFrameX = bitmapResource.getWidth();
}
}
}
/**
* Render the text
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRGB(255, 255, 255);
// Draw Rectangle 1250 pixel
Rect rect1000 = new Rect();
rect1000.set(0, 0, 1250, 20);
Paint blue = new Paint();
blue.setColor(Color.BLUE);
canvas.drawRect(rect1000, blue);
// Draw first Bitmap
Rect rectBitmapSource = new Rect(0, 0, bitmapResource.getWidth(),
bitmapResource.getHeight());
Rect rectBitmapDestinationFirst = new Rect(leftFrameX, offsetYInPixel,
rightFrameX, offsetYInPixel + bitmapResource.getHeight());
canvas.drawBitmap(bitmapResource, rectBitmapSource,
rectBitmapDestinationFirst, null);
// Draw second Bitmap
Rect rectBitmapDestinationSecond = new Rect(
(leftFrameX + bitmapResource.getWidth()), offsetYInPixel,
(rightFrameX + bitmapResource.getWidth()), offsetYInPixel
+ bitmapResource.getHeight());
canvas.drawBitmap(bitmapResource, rectBitmapSource,
rectBitmapDestinationSecond, null);
}
}
I figured out the answer after going throught the googledocs.
if you want to invalidate a View from outside of the UI-Thread you have to use the
postinvalidate() command instead of invalidate().
Thats it. It will work like charme!!!

Categories

Resources