Invalidate() method on onDraw in android - java

I'm doing a project in android studio. I just want to do a flashing light point using onDraw() and invalidate() but something is wrong.
This is the first class
public class flashingPoint extends View {
private ShapeDrawable mParteDibujable;
public flashingPoint(Context context){
super(context);
final Handler bridge = new Handler();
Thread time = new Thread() {
public void run() {
bridge.postDelayed(this, 1000);
invalidate();
}
};
time.start();
}
#Override
protected void onDraw(Canvas canvas) {
mParteDibujable = new ShapeDrawable(new OvalShape());
mParteDibujable.getPaint().setColor(0xff74AC23);
mParteDibujable.setBounds(10, 20, 80, 80);
mParteDibujable.draw(canvas);
//invalidate();
}
And then the main class:
public class MainActivity extends AppCompatActivity {
private ShapeDrawable mParteDibujable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout mLinearLayout = new LinearLayout(this);
flashingPoint myView = new flashingpoint(this);
mLinearLayout.addView(myView);
setContentView(mLinearLayout);
}
}

If you have the timer, you neither need nor want the invalidate in onDraw. Invalidating in onDraw is both logically weird and will lead to poor results- it would either be ignored, or it would cause an immediate redraw. Neither is desired.
Also, you can't invalidate on a Thread, you'd need to use postInvalidate. And your thread is wrong- either don't use a thread, use a Runnable and post it to the Handler, or the thread should infinitely loop, not return. Preferably the first, there's no reason to have a thread here at all.

Related

How do I stop all handlers running in the background?

I'm writing an application that is creating audio players dynamically. By pressing a button, I want all handlers to stop and start again when I call them. The handlers, basically, are responsible for updating the seek-bar of the audio player. the seek-bar and the play button are being creating into a linearlayout, and this linear layout is being adding into a parent linearlayout. this way, for example, if I decide by code to create 4 audio players and I want them to be able to play the audio together, I put in a loop the creation of a 4 linearlayouts and add them to the parent linearlayout. the code more or less looks like this (this is just the relevant part, not the real code...):
for (int i = 0; i < 4; i++)
{
LinearLayout audioLayout = new LinearLayout(this);
audioLayout.setOrientation(LinearLayout.HORIZONTAL);
audioLayout.setPadding(20, 20, 20, 20);
final Button btnAudioPlay = new Button(this);
btnAudioPlay.setBackgroundResource(R.drawable.play);
LinearLayout.LayoutParams playButtonParams = new LinearLayout.LayoutParams(50, 50);
playButtonParams.setMargins(20, 0, 20, 0);
audioLayout.addView(btnAudioPlay, playButtonParams);
final SeekBar sbAudioPosition = new SeekBar(this);
LinearLayout.LayoutParams seekbarParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
seekbarParams.setMargins(20, 0, 20, 0);
audioLayout.addView(sbAudioPosition, seekbarParams);
parentLayout.addView(audioLayout);
final MediaPlayer mpAudio = MediaPlayer.create(this, Uri.fromFile(audFile));
mpAudio.setLooping(false);
mpAudio.seekTo(0);
int totalTime = mpAudio.getDuration();
sbAudioPosition.setMax(totalTime);
sbAudioPosition.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
{
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
if (fromUser)
{
mpAudio.seekTo(progress);
sbAudioPosition.setProgress(progress);
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
mpAudio.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
btnAudioPlay.setBackgroundResource(R.drawable.play);
sbAudioPosition.setProgress(0);
}
});
btnAudioPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!mpAudio.isPlaying()) {
mpAudio.start();
btnAudioPlay.setBackgroundResource(R.drawable.pause);
final Handler handler = new Handler();
runOnUiThread(new Runnable() {
#Override
public void run() {
sbAudioPosition.setMax((int) mpAudio.getDuration());
int currentPossition = mpAudio.getCurrentPosition();
sbAudioPosition.setProgress(currentPossition);
handler.postDelayed(this, 0);
}
});
}
else {
mpAudio.pause();
btnAudioPlay.setBackgroundResource(R.drawable.play);
}
}
});
}
When I press a button, I'm updating the view, and I want all handlers to stop, but it doesn't happening... I tried to declare, before the onCreate the Handler handler = null; (instead of the final handler) and then in the linearlayout make it new and it worked, but in the method of the button I called handler.removeCallbacksAndMessages(null); and it didn't work. I tried to declare the runable as well and call handler.removeCallbacks(runOnUiThread);, but my application crashes... :(
What do I need to do to solve it?
If I want that one audio play will stop the current audio playing, is it possible? will it solve my problem?
Sorry for the length and thanks for the helpers. :)
You can use the Handler as a member of your Activity something like:
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
#Override
public void run() {
sbAudioPosition.setMax((int) mpAudio.getDuration());
int currentPossition = mpAudio.getCurrentPosition();
sbAudioPosition.setProgress(currentPossition);
mHandler.post(this);
}
};
Then use the mHandler when you make execute mHandler.postDelayed(runnable,delay). And then you can call mHandler.removeCallbacks(null); when you want to remove them all.
Note: You can remove specific runnables like this: mHandler.removeCallbacks(mRunnable);

AsyncTask: invalidating view does not take effect

Update
My small showcase is stored on Bitbucket
https://bitbucket.org/solvapps/animationtest
I have an Activity with a view in it. Contentview is set to this view.
public class MainActivity extends AppCompatActivity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(myView);
startMovie();
}
public void startMovie(){
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
}
A MovieTask is an Asynctask and refreshes the view periodically.
But invalidate() doesn't refresh the view.
public class MovieTask extends AsyncTask<String, String, String> {
MyView drawingView;
MainActivity mainActivity;
public MovieTask(MyView view, MainActivity mainActivity){
this.mainActivity = mainActivity;
this.drawingView =view;
}
#Override
protected String doInBackground(String... strings) {
for(int i=20;i<100;i++){
drawingView.myBall.goTo(i,i);
publishProgress();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
mainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Log.v("DEBUG_DRAW","in onProgressUpdate()");
drawingView.invalidate();
}
});
}
}
Can someone help ?
See how you are launching the AsyncTask:
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
You are manually calling a method inside some class called MovieTask, thus you are running a code on the same thread. Obviously, that is not your intention, you intended to run the computation code on a background thread.
Correct way to launch AsyncTask is using execute(Params...):
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.execute("");
}
Now you will get the desired effect.
P.S.
Please, do not use that code: you do not need to launch a background thread in order to do that kind of stuff. As an alternative consider Animators API.
Declare setBall(int pos) method inside MyBall class:
public class MyView extends View {
...
public void setBall(int pos) {
myBall.setX(pos);
myBall.setY(pos);
invalidate();
}
}
Then change startMovie() to following:
public void startMovie() {
// "ball" means, that Animators API will search for `public setBall(int)` method inside MyView.java and call that method
ObjectAnimator ball = ObjectAnimator.ofInt(myView, "ball", 20, 100);
ball.setDuration(1000);
ball.start();
}
You'll get the same animation without a nasty code.
There is two possible case, first as described in documents:
void invalidate ()
Invalidate the whole view. If the view is visible,
onDraw(android.graphics.Canvas) will be called at some point in the
future.
So try to run your code in onResume, there is a chance that View is not visible yet.
Secondly View#invalidate tells the system to redraw the view as soon as the main UI thread goes idle. That is, calling invalidate schedules your view to be redrawn after all other immediate work has finished.
If you'd like to have your view updated periodically use Handler#postDelay or run it in a separate thread and use View#postInvalidate to update the View and trigger the call to onDraw.

SurfaceView getHolder returns null

I read other questions on this, but I feel like my architecture is a bit different, so I'm posting.
I have a game with one MainActivity and multiple views. Basically, there is one LinearLayout that fills MainActivity, and different views are set on the layout depending on the state. For example, menuState (menuView), gameState (gameView), gameOverState (gameOverView).
In my BattleView (which extends SurfaceView), I tried to implement a game loop. However, my BattleView cannot implement a Callback due to the fact getHolder() returns null.
How would I go on about fixing this?
public class MainActivity extends AppCompatActivity {
GameScreen gs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.init();
}
private void init(){
this.gs = new GameScreen(this);
this.hideSystemUI(this.gs);
setContentView(this.gs);
}
Then my GameScreen class contains BattleView class, which is called when appropriate.
public GameScreen(Context context){
super(context);
this.init();
}
private void init(){
this.ms = new MapScreen(this.getContext(), this);
((MainActivity)this.getContext()).hideSystemUI(this.ms);
this.addView(this.ms);
}
private void newMapScreen(){
this.ms = new MapScreen(this.getContext(), this);
}
public void addBattleScreen(BattleScreen bs){
//this.bs = new BattleScreen(this.getContext(), ms);
this.removeView(this.ms);
this.bs = bs;
((MainActivity)this.getContext()).hideSystemUI(this.bs);
this.addView(this.bs);
}
Well.. BattleScreen contains BattleView class and is initialized there. Then, BattleView class
public BattleView(Context context, int monsterNumber, Hero hero){
super(context);
this.gameLoopInit();
this.setBackgroundImage();
}//end
private void gameLoopInit(){
holder = getHolder(); <------------- ERROR (null)
System.out.println(holder);
holder.addCallback(this);
}

Once using the override method and once more using without the override method and two different result

The answer for this question was useful for me. But I have a new question about that. I read the Java grammar but I have problems in some part of the Java grammar and for this reason, I ask my questions here. I changed two part of the code link as the following:
In Draw.java:
I changed the public void onDraw(Canvas canvas) To the public void d(Canvas canvas)
And I added the following in MainActivity.java
public class MainActivity extends Activity {
Draw draw;
Cal cal;
TextView textView;
RelativeLayout linearLayout;
Canvas canvas;
public void onCreate(Bundle s) {
super.onCreate(s);
setContentView(R.layout.activity_main);
linearLayout = (RelativeLayout) findViewById(R.id.t);
cal = new Cal(this);
cal.cal();
textView = new TextView(getApplicationContext());
textView.setText("" + cal.result);
textView.setTextColor(Color.RED);
draw = new Draw(this);
draw.d(canvas);
linearLayout.addView(textView);
linearLayout.addView(draw);
}}
the code can compile and install successfully.But only it can run for a short time in my device and also it can not run in AVD manager.
I'm sure the grammar used is correct.But I do not know what is the cause of the collision in the code that I can not see the output Code correctly.
UPDATE, Draw.java
public class Draw extends View {
Paint paint = new Paint();
Draw(Context context) {
super(context);
}
public void d(Canvas canvas) {
paint.setColor(Color.BLUE);
canvas.drawCircle(120,120,40,paint);
}
}
I don't try it, maybe it's stupid but try to add public front of the Draw constructor:
**public** Draw(Context context) {
super(context);
}
And in d() method, there should be one less parenthesis.

Constructor wont run it's code

I want to run a gameloop code inside my GameLoop class constructor in my activity, but it seems that it got no respond.
I've tried to put the code inside the OnCreate method instead of a new class and that worked.
My Activiy Class:
public class GameActivity extends Activity {
private Button btnHertz;
private TextView textView1;
private GameLoop gameloop;
private int hertz = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
gameloop = new GameLoop();
btnHertz = (Button) findViewById(R.id.btnHertz);
textView1 = (TextView) findViewById(R.id.testTextView1);
}
public void testUpdate(){
hertz++;
textView1.setText(Integer.toString(hertz));
}
GameLoop Class:
public class GameLoop {
private GameActivity gui;
public GameLoop() {
gui = new GameActivity();
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(new Runnable() {
public void run() {
gui.testUpdate();
}
}, 0, 10, TimeUnit.MILLISECONDS);
}
gui = new GameActivity();
The activity object you're passing updates to is different from the one that displays your UI.
Never instantiate activities yourself with new. Their lifecycle methods won't be invoked and they won't be good for anything. In this case you'd get an NPE at textView1.setText() since onCreate() has not been run.
Instead, pass a GameActivity reference as an argument to GameLoop, e.g.
... new GameLoop(this)
...
public GameLoop(GameActivity gui) {

Categories