Reputation: 815
I am trying to create an app that when you touch the screen, a snowflake appears at the top of the screen and slowly falls. I am adding each snowflake to an arraylist so that I can make each snowflake fall. Here is my code:
Runnable runable = new Runnable(){
@Override
public void run(){
while(true){
letTheSnowFall();
}
}
};
public void letTheSnowFall(){
for(int i = 0; i < snowArray.size(); i++){
snowArray.get(i).setY(snowArray.get(i).getY() + 0.01f);
}
}
and the I start the thread in the onCreate() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (RelativeLayout) findViewById(R.id.activity_main);
setContentView(layout);
layout.setOnClickListener(listener);
Thread myThread = new Thread(runable);
myThread.start();
}
Upvotes: 0
Views: 4168
Reputation: 535
To update the view, you need to update it from the main thread. Clearly, you are doing it wrong. You can throw the information to the main thread and update it. Perhaps, you could try updating the view using Android built threads (AsyncTasks), compute the results on doInBackGround(), and publish the results on onPostExecute() method.
Upvotes: 1
Reputation: 20159
I'm guessing your snowArray
contains a bunch of instances of View
(or one of its many subclasses)? In that case, you're trying to modify a GUI component from a non-GUI thread, which is not allowed and thus throws a CalledFromWrongThreadException
.
The "easy" solution would be to call Activity.runOnUiThread
wherever you need to modify the UI. This is useful if you've done some processing on a separate thread and are now ready to show the results on the GUI.
However, you want to do a lot of GUI updates. It's even worse, since you're doing updates in an infinite loop: while(true) { letTheSnowFall(); }
. This thing is truly infinite: it will never stop and your snow will just keep falling forever, with the Y-coordinate rapidly incrementing and eventually overflowing. You won't see that happen though since as soon as the GUI has updated, you're immediately updating it again: there is no delay.
What you're trying to do is much simpler though: you just want to let the snow fall. You want to animate your snow flake views starting from their current position and ending some distance downward. Android has a whole package for animations along with some very good guides.
Upvotes: 0
Reputation: 1895
If I understand correctly the snowflakes are views. So the snowArray has the type ArrayList<SubtypeOfView>
. If so then the exception is throw because you call View.setY()
method from a thread other than the main thred. Almost all of the methods related to the Android GUI framework have to be called from the main thread(the thread that executes callbacks such as Activity.onCreate()
).
Upvotes: 0
Reputation: 1006584
First, you cannot update your UI from a background thread.
Second, an infinite background thread with no delays is exceedingly bad code.
Third, please do not call setContentView()
twice in onCreate()
. You do not need the second one (setContentView(layout)
).
The lightest-weight way to accomplish this is to use postDelayed()
, available on any View
(such as layout
), to arrange to get control in the future after a delay. You can cancel the postDelayed()
work by calling removeCallbacks()
, passing in the same Runnable
as you used with postDelayed()
:
/***
Copyright (c) 2012 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
From _The Busy Coder's Guide to Android Development_
http://commonsware.com/Android
*/
package com.commonsware.android.post;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class PostDelayedDemo extends Activity implements Runnable {
private static final int PERIOD=5000;
private View root=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
root=findViewById(android.R.id.content);
}
@Override
public void onResume() {
super.onResume();
run();
}
@Override
public void onPause() {
root.removeCallbacks(this);
super.onPause();
}
@Override
public void run() {
Toast.makeText(PostDelayedDemo.this, "Who-hoo!", Toast.LENGTH_SHORT)
.show();
root.postDelayed(this, PERIOD);
}
}
(code from this sample project)
In your case, run()
would update your snowflakes instead of showing a Toast
.
Upvotes: 1