Dynamically change background color with animated

2020-08-01 07:07发布

问题:

I'm trying to generate a random color and set it as the background at the rate of 3 seconds. I have created a thread that will handle this change, now I would like to add a transition between the color changes to make it blend well.

As a reference, take a look at this app.

EDIT: I've tried using an ObjectAnimator and ArgbEvaluator in a loop with a 3 second transition period, but the screen keeps flashing in a strobe-like way that will just give you a headache. Besides that, the colors change just fine and everything else is perfect. Could somebody run this and see what could be going wrong?

public class Main extends Activity {

public int color1, color2, red1, red2, blue1, blue2, green1, green2;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.main);


    new Thread() {
        public void run() {
            while(true) {
                try {
                    Thread.sleep(3000); // I've also tried 1000 and 4000, same issue.
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.this.runOnUiThread(new Runnable() {
                    public void run() {

                    //generate color 1
                        red1 = (int)(Math.random() * 128 + 127);
                        green1 = (int)(Math.random() * 128 + 127);
                        blue1 = (int)(Math.random() * 128 + 127);
                        color1 = 0xff << 24 | (red1 << 16) |
                                (green1 << 8) | blue1;


                    //generate color 2

                        red2 = (int)(Math.random() * 128 + 127);
                        green2 = (int)(Math.random() * 128 + 127);
                        blue2 = (int)(Math.random() * 128 + 127);
                        color2 = 0xff << 24 | (red2 << 16) |
                                (green2 << 8) | blue2;

                    //start animation
                        View v = findViewById(R.id.view);
                        ObjectAnimator anim = ObjectAnimator.ofInt(v, "backgroundColor", color1, color2);


                        anim.setEvaluator(new ArgbEvaluator());
                        anim.setRepeatCount(ValueAnimator.INFINITE);
                        anim.setRepeatMode(ValueAnimator.REVERSE);
                        anim.setDuration(3000);
                        anim.start();

                    }
                });
            }
        }
    }.start();
}

}

EDIT: I've narrowed it down and found the ".setRepeatMode" was causing the problem. I still don't have a fix. By changing the "Reverse" to something else (Infinite or other provided options) it prevents the animation from happening. Any idea what I can do to fix this?

ALSO, does anybody know a better way to generate more vibrant colors? Everything I looked into is outdated.

回答1:

You are doing everything correctly except for one thing: Every 3 seconds, you are randomly generating 2 colors. So, this is what's happening:

1st iteration:

color1 is generated

color2 is generated

View's background is set to color1. And then the background changes from color1 to color2.

// All's good

2nd iteration:

There's a new color1

There's a new color2

View's background is set to new color1. Immediate change causes the strobe light effect. And then the background changes from new color1 to new color2.

What you should do to fix this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.main);

    // Generate color1 before starting the thread
    red1 = (int)(Math.random() * 128 + 127);
    green1 = (int)(Math.random() * 128 + 127);
    blue1 = (int)(Math.random() * 128 + 127);
    color1 = 0xff << 24 | (red1 << 16) |
                          (green1 << 8) | blue1;


    new Thread() {
        public void run() {
            while(true) {
                try {
                    Thread.sleep(3000); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.this.runOnUiThread(new Runnable() {
                    public void run() {

                    //generate color 2

                        red2 = (int)(Math.random() * 128 + 127);
                        green2 = (int)(Math.random() * 128 + 127);
                        blue2 = (int)(Math.random() * 128 + 127);
                        color2 = 0xff << 24 | (red2 << 16) |
                                (green2 << 8) | blue2;

                    //start animation
                        View v = findViewById(R.id.view);
                        ObjectAnimator anim = ObjectAnimator.ofInt(v, "backgroundColor", color1, color2);


                        anim.setEvaluator(new ArgbEvaluator());
                        anim.setRepeatCount(ValueAnimator.INFINITE);
                        anim.setRepeatMode(ValueAnimator.REVERSE);
                        anim.setDuration(3000);
                        anim.start();

                        // Now set color1 to color2
                        // This way, the background will go from
                        // the previous color to the next color
                        // smoothly
                        color1 = color2;

                    }
                });
            }
        }
    }.start();
}

So, from second iteration onwards, the starting color should be the same as the ending color for the previous iteration. Initialize/generate color1 only once: before starting the thread. After anim.start(), add:

color1 = color2;

Also, note that you are creating a new ObjectAnimator every 3 seconds:

ObjectAnimator anim = ObjectAnimator.ofInt(v, "backgroundColor", color1, color2);

So, the following statements have no effect:

anim.setRepeatCount(ValueAnimator.INFINITE);
anim.setRepeatMode(ValueAnimator.REVERSE); 

Here's what I would recommend:

public class Main extends Activity {

    public int color1, color2, red1, red2, blue1, blue2, green1, green2;

    View v;

    ObjectAnimator anim;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.main);

        // White or whatever color background R.id.view
        // has at the beginning
        color1 = 0xffffffff;

        v = findViewById(R.id.llMain);

        // We haven't initialized color2 yet. Will set this later
        anim = ObjectAnimator.ofInt(v, "backgroundColor", color1);

        anim.setEvaluator(new ArgbEvaluator());

        anim.setDuration(3000);


        new Thread() {
            public void run() {
                while(true) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Main.this.runOnUiThread(new Runnable() {
                        public void run() {

                            //generate color 2

                            red2 = (int)(Math.random() * 128 + 127);
                            green2 = (int)(Math.random() * 128 + 127);
                            blue2 = (int)(Math.random() * 128 + 127);
                            color2 = 0xff << 24 | (red2 << 16) |
                                    (green2 << 8) | blue2;

                            // Update the color values
                            anim.setIntValues(color1, color2);

                            anim.start();

                            // Order the colors
                            color1 = color2;

                        }
                    });
                }
            }
        }.start();
    }
}

This way, you're creating an ObjectAnimator object once, and updating the color values every 3 seconds.



回答2:

How about use ObjectAnimator w/ ArgbEvaluator. Using ObjectAnimator, you can animate any property easily if the object has a proper setter method in camel case(in the form of set()). In your case, View has setBackgroundColor, so you can try this:

View v = findViewById(R.id.mask2);
ObjectAnimator anim = ObjectAnimator.ofInt(v, "backgroundColor", Color.RED, Color.BLUE);
anim.setDuration(3000);
anim.setEvaluator(new ArgbEvaluator());
anim.setRepeatCount(ValueAnimator.INFINITE);
anim.setRepeatMode(ValueAnimator.REVERSE);
anim.start();

For more general & detail description about Property Animation:

http://developer.android.com/guide/topics/graphics/prop-animation.html#object-animator

Unfortunately, this new api is only supported API11+, so if you concern about compatibility issue, you can try to use NineOldAndroids library written by JakeWharton.



回答3:

Yet another alternative how to change the color of the background;

    ColorDrawable[] color = { new ColorDrawable(Color.RED), new ColorDrawable(Color.WHITE) };
    TransitionDrawable trans = new TransitionDrawable(color);
    view.setBackgroundDrawable(trans);
    trans.startTransition(3000);

This approach also has methods like trans.reverseTransition(duration) available.