Search code examples
javaandroiduser-interfacesleepinvalidation

update background color with short sleeps in between in Android application


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?

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.