I am trying to make a rainbow colored title (which changes with time), here is the onCreate:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
new Thread(TitleColorRunnable).start();
}
And the corrosponding runnable:
Runnable TitleColorRunnable = new Runnable()
{
TextView title;
int titleLength;
Spannable spannable;
int i;
float mainHue = 15;
float hue;
int color;
@Override
public void run()
{
title = findViewById(R.id.titleTextView);
title.setText("TITLE EXAMPLE", TextView.BufferType.SPANNABLE);
titleLength = title.length();
spannable = (Spannable) title.getText();
while (true)
{
for (i = 0; i < titleLength - 1; i++)
{
hue = (mainHue - i) % 360;
color = Color.HSVToColor(new float[]{hue, 1, 1});
title.post(new Runnable()
{
@Override
public void run()
{
spannable.setSpan(new ForegroundColorSpan(color), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
});
}
mainHue++;
if (mainHue == 360)
{
mainHue = 0;
}
try
{
Thread.sleep(10);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
};
This thread slows down over time, and starts to slowly bug down the entire UI until everything stops completely.
Is it possible that the line
spannable.setSpan(new ForegroundColorSpan(color), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
keep saving new ForegroundColorSpan variables to memory?
Please help, thank you!
Ref. the line:
title.post(new Runnable()...
There is nothing to guarantee that the Runnable has completed, and been dequeued, by the time you call title.post()
the next time. The queue of Runnables inside your app's Looper is probably getting super backlogged. IOW, you're queueing Runnables more quickly than they can be executed, and eventually your UI thread has to spend all its time dealing with those, instead of doing other things you'd like it to do, such as responding to user input and so forth.
A contributing factor is your 10ms delay: Thread.sleep(10)
. 100Hz is a little fast for this kind of update; 30Hz ought to be plenty (for human perception), or even slower, since you're just barely changing the color.
Suggested Fix
TitleColorRunnable
logic, and move it to onCreate()
.myRunnable.run()
, execute title.postDelayed( myRunnable, 33 )
. This is the trick; this is what keeps the iteration going, and this is what stops the Looper queue from filling up. Since myRunnable
re-queues itself, there's never more than one myRunnable
in the queue. (It's not recursion; you're simply taking advantage of the Android message queue mechanism to schedule your Runnable.) 33ms is for a 30Hz update rate.title.post( myRunnable )
to onCreate()
.mainHue = mainHue + 3;
instead, to keep the color changing at roughly the same rate.Obviously, with this solution you have to change the state of mainHue
inside myRunnable.run()
.