Question

I'm trying to update the background color of a few TextViews in my Android application. I want to set each color with a short pause in between, ie:

<set TextView one's background to gold>
<pause 500ms>
<set TextView two's background to gold>
<pause 500ms>
<set TextView three's background to gold>
<pause 500ms>

The goal is to make some sort of progressive highlighting pattern across the boxes.

The issue that I'm having is all the boxes are updating at once, after the pause. I've read that if I want to force the view to draw itself I need to call invalidate() and I've tried that, but it didn't seem to do anything. I'm not sure if either the method I'm using to sleep is causing the issues, or if I'm not forcing the draw update with the correct method. Hopefully someone can point me in the right direction:

    Resources res = getResources();
    for(int i=0; i<3; i++){
        int id = res.getIdentifier("txt"+Integer.toString(box[i]), "id",
                                    getApplicationContext().getPackageName());
        TextView temp = (TextView)findViewById(id);

        // Set gold with 50% opacity
        temp.setBackgroundColor(Color.parseColor("#ffd700"));
        temp.getBackground().setAlpha(127);

        // Force redraw
        temp.invalidate();

        // Sleep for half a second
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

Note: I want the main thread blocked until this sequence of background changes is updated, nothing should happen until these three TextViews have been updated, that's why I'm calling sleep() from the main UI thread.


EDIT
I attempted to use a runnable while "blocking" my main thread to do the updates in the background. If this is the way it should be handled maybe someone can point out to me where I'm going wrong:

    boolean moveon;
    //...
    onCreate() {
        moveon = false;
    //...

    Handler myh = new Handler();
    Runnable myr = new Runnable() { 
        public void run() {
            Resources res = getResources();
            for(int i=0; i<3; i++){
                id = res.getIdentifier("txt"+Integer.toString(winners[i]), "id",
                        getApplicationContext().getPackageName());      
                TextView temp = (TextView)findViewById(id);
                temp.setBackgroundColor(Color.parseColor("#ffd700"));
                temp.getBackground().setAlpha(127);
                temp.invalidate();
            }
            moveon = true;
        }
    };
    myh.post(myr);
    while(moveon == false){}   //blocks forever here, perhaps the update to from the
                               // runable doesn't propagate?
Was it helpful?

Solution

Going to take the comments I made and post them here as well:

"I want the main thread blocked" -- No, you want the UI disabled during the period, not blocked, which is why they're all occurring at once. A better option would be a global boolean, let's call it "enabled," and set that to false during the operation, then to true when the updates have completed, while calling postInvalidate() from the secondary thread. Then in your UI thread you should check to see if enabled is equal to true before allowing any additional operations to take place. This will create the illusion of being blocked without it actually being blocked

Now, taking your code:

boolean moveon;
//...
onCreate() {
    moveon = false;
//...

Handler myh = new Handler();
Runnable myr = new Runnable() { 
    public void run() {
        Resources res = getResources();
        for(int i=0; i<3; i++){
            id = res.getIdentifier("txt"+Integer.toString(winners[i]), "id",
                    getApplicationContext().getPackageName());      
            TextView temp = (TextView)findViewById(id);
            temp.setBackgroundColor(Color.parseColor("#ffd700"));
            temp.getBackground().setAlpha(127);
            temp.invalidate();
        }
        moveon = true;
    }
};
myh.post(myr);
while(moveon == false){}  

You're close:

boolean moveon;
//...
onCreate() {
    moveon = false;
//...

// Still in UI thread here
Handler myh = new Handler();
final Resources res = getResources(); // final so we can access it inside second thread
new Thread(new Runnable() { 
    public void run() {
        // In second thread
        for(int i=0; i<3; i++){
            // Now we use the Handler to post back to the UI thread from the second thread
            myh.post(new Runnable() { 
                public void run() {
                   // In UI thread, update here and then invalidate
                   id = res.getIdentifier("txt"+Integer.toString(winners[i]), "id",
                    getApplicationContext().getPackageName());      
                   TextView temp = (TextView)findViewById(id);
                   temp.setBackgroundColor(Color.parseColor("#ffd700"));
                   temp.getBackground().setAlpha(127);
                   temp.postInvalidate();
                }
            });

            Thread.sleep(delayMs); // Sleep second thread
        }
        myh.post(new Runnable() { 
                public void run() {
                   // In UI thread, reset the moveon to true on correct thread
                   moveon = true;
                }
            });
    }
}).start();

Then don't use your while-loop, as that will still just block the UI and give you the same problem. What you want to do instead is check if moveon == true before allowing the user to do anything else; no loop necessary.

OTHER TIPS

Not sure if its really fits your exact requirement ,but you can try with ValueAnimator by setting delay to update animation color.

Here is the sample code which I tried with two textviews.

    final TextView textView1 = (TextView) findViewById(R.id.textView1);
    final TextView textView2 = (TextView) findViewById(R.id.textView2);

    final ValueAnimator colorAnimation1 = ValueAnimator.ofObject(new ArgbEvaluator(), Color.YELLOW, Color.YELLOW);
    final ValueAnimator colorAnimation2 = ValueAnimator.ofObject(new ArgbEvaluator(), Color.YELLOW, Color.YELLOW);

    colorAnimation1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animator) {
            textView1.setBackgroundColor((Integer) animator.getAnimatedValue());
            colorAnimation2.setStartDelay(500);
            colorAnimation2.start();

        }

    });
    colorAnimation1.setStartDelay(500);
    colorAnimation1.start();

    colorAnimation2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animator) {
            textView2.setBackgroundColor((Integer) animator.getAnimatedValue());
        }
    });
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top