14

I want to create a Button in Android with a text, and a background image. The background image should crossfade every X time.

I have this working using a TransitionDrawable with 2 images.

But I can't get this to work with more than 2 images.

What I have :

In Java code I create a Button and set a background (which is a TransitionDrawable defined in XML). And I start the transition.

final Button b = new Button(getApplicationContext());
b.setTextColor(getResources().getColor(R.color.white));
b.setText("Some text");
b.setBackgroundDrawable(getResources().getDrawable(R.drawable.tile));
StateListDrawable background = (StateListDrawable) b.getBackground();
TransitionDrawable td = (TransitionDrawable) background.getCurrent();
td.startTransition(2000);

In XML I define in tile.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape>
            <solid
                android:color="#449def" />
        </shape>
    </item>
    <item android:drawable="@drawable/transition">
        <shape>
            <solid
                android:color="#0000ff" />
        </shape>
    </item>
</selector> 

And finally a transition.xml

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android"     android:oneshot="false">
  <item android:drawable="@drawable/desert"/> 
  <item android:drawable="@drawable/hydrangeas" /> 
  <item android:drawable="@drawable/jellyfish" /> 
</transition> 

Now the effect is that when I start the app the desert image is shown. This image crossfades to the hydrangeas image as it should. But the jellyfish image is never shown.

In the doc for TransitionDrawables it is stated that you can specify more than 2 drawables but I can't get this to work.

I also tried this without any XML but in pure JAVA but this gave exactly the same problem :-(

Yogesh Umesh Vaity
  • 41,009
  • 21
  • 145
  • 105
Knarf
  • 2,077
  • 1
  • 16
  • 24
  • *In the doc for TransitionDrawables it is stated that you can specify more than 2 drawables* - can you provide a link to where this is stated? – user Mar 27 '13 at 10:45
  • http://developer.android.com/reference/android/graphics/drawable/TransitionDrawable.html#TransitionDrawable(android.graphics.drawable.Drawable[]) This states "At least 2 layers are required for this drawable to work properly.". As said in my original post I also tried everything in pure Java code (thus actaully using this constructor) but this had exactly the same problem. – Knarf Mar 27 '13 at 11:17
  • 1
    A bad choice for words. I've just looked at the code for `TransitionDrawable` and it only fades between two drawables, every other layer is ignored. – user Mar 27 '13 at 11:47
  • thanks Luksprog In that case I will do some workarounds to achieve what I want. – Knarf Mar 27 '13 at 12:11
  • Maybe you could simply use two TransitionDrawables, one containing the first and second drawables and the other containing the same second drawable plus the third drawable. – user Mar 27 '13 at 12:24
  • That is what I am doing now. I have a number of pictures (which is unknown). And everytime a transitionDrawable is completed I create a new TransitionDrawable with the next images and set this as the new backgroundDrawable on the button. So all is working now. Thanks again for the hint ! – Knarf Mar 27 '13 at 12:58
  • @Knarf I'm trying something similar. Could you tell me how did you detect if the transition has completed? Also, you might to answer and accept your question (when available) if you found a suitable solution. – RobGThai Jun 06 '13 at 03:49
  • @RobGThai did you find how to detect if the transition has completed? – ecem Jun 11 '13 at 23:03
  • @ecem Nothing so far. I'm looking into creating my own version of TransitionDrawable. Things hasn't been good on that end either. – RobGThai Jun 12 '13 at 04:34
  • @RobGThai I see, I'm thinking of creating a thread and waiting it for the transition time and then changing the TransitionDrawable with the new one, do you think it will work? – ecem Jun 12 '13 at 10:03

6 Answers6

7

You can do it by using a handler

mAnimateImage is your button

int DrawableImage[] = {R.drawable.back_red, R.drawable.back_green, R.drawable.back_purple};

final Handler handler = new Handler();
    final int[] i = {0};
    final int[] j = {1};
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Resources res = getApplicationContext().getResources();
                    TransitionDrawable out = new TransitionDrawable(new Drawable[]{res.getDrawable(DrawableImage[i[0]]), res.getDrawable(DrawableImage[j[0]])});
                    out.setCrossFadeEnabled(true);
                    mAnimateImage.setBackgroundDrawable(out);
                    out.startTransition(4000);
                    i[0]++;
                    j[0]++;
                    if (j[0] == DrawableImage.length) {
                        j[0] = 0;
                    }
                    if (i[0] == DrawableImage.length) {
                        i[0] = 0;
                    }
                    handler.postDelayed(this, 8000);
                }
            });
        }
    }, 0);
6

According to the official documentation, TransitionDrawable can only cross-fade among 2 layers, quoting from the official android reference.

An extension of LayerDrawables that is intended to cross-fade between the first and second layer. To start the transition, call startTransition(int). To display just the first layer, call resetTransition().

If you don't read it carefully, since it extends LayerDrawables, which can have multiple layers, one may expect that you could cross-fade from N layers. But it is very clear, startTransition shows the second layer, resetTransition shows the first.

I suggest you do your own implementation for multiple transitions. What I'd do is to have 2 images and animate them. You may need to set the drawables by hand, but it should be a quite simple piece of code.

shalafi
  • 3,926
  • 2
  • 23
  • 27
  • Actually what happens for me is that it plays the first transition fine and then when I call startTransition again it jumps to the first drawable and transitions back to the second one. – CaseyB Oct 18 '13 at 17:44
  • 3
    You are right! I reviewed the documentation and updated the answer. It is documented that it only works with 2 layers. – shalafi Oct 24 '13 at 09:58
4

in appendix operating time you can dynamically change pictures

Use td.setDrawableByLayerId(td.getId(1), drawable) on your TransitionDrawable

TransitionDrawable transitionDrawable = (TransitionDrawable) myImage
                            .getDrawable();
transitionDrawable.setDrawableByLayerId(transitionDrawable.getId(1), getResources()
                            .getDrawable(R.drawable.c));
Sergei S
  • 2,553
  • 27
  • 36
1

You can only transition maximum two images with TransitionDrawable. To work with more than two images you can extend LayerDrawable and implement your own TransitionDrawable.

Here's the ready implementation of custom TransitionDrawable to work with more than two images.

You can see the complete sample along with the gif demo here on Github.

Yogesh Umesh Vaity
  • 41,009
  • 21
  • 145
  • 105
0

No need to use handler, it's more efficient + cleaner to use coroutines instead of a handler and as a bonus you can make this transition between as many drawables as you like:

    /**
     * Run multitransition animation on a view.
     *
     * @param drawableIds The drawables to load into the view, one after the other
     * @param delayBetweenTransitions the number of milliseconds to transition from one drawable to the next
     * @param viewToAnimate The view to animate.
     */
    fun runMultitransitionAnimation(@DrawableRes vararg drawableIds: Int, 
                                    delayBetweenTransitions : Int, 
                                    viewToAnimate : View) {
        CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main.immediate) {
            val delay = SOME_INTEGER_MILLISECONDS
            for (i in 0 until drawableIds.size-1) {
                val nextTransition = TransitionDrawable(arrayOf(
                    context.getDrawable(drawableIds[i]),
                    context.getDrawable(drawableIds[i+1])))
                ))
                nextTransition.isCrossFadeEnabled = true
                viewToAnimate.background = nextTransition
                nextTransition.startTransition(delayBetweenTransitions)
                delay(delayBetweenTransitions*2)
            }
        }
    }
Hisham Hijjawi
  • 1,803
  • 2
  • 17
  • 27
-1

You'll need to just make a new TransitionDrawable per iteration.

This will step up through your index of all the images you add, you can have any number of images...

private Handler handler = new Handler();

private void startImageCrossfading(@IdRes int imageViewResId, @DrawableRes int... drawableIds) {

    Drawable[] bitmapDrawables = new Drawable[drawableIds.length];

    for (int i = 0; i < drawableIds.length; i++) {
        // if you are using Vectors cross fade won't work unless you do this! - See my answer at https://stackoverflow.com/a/54583929/114549
        // bitmapDrawables[i] = getBitmapDrawableFromVectorDrawable(this, drawableIds[i]);
        bitmapDrawables[i] = ContextCompat.getDrawable(this, drawableIds[i]);
    }

    final ImageView imageView = findViewById(imageViewResId);

    handler.postDelayed(new Runnable() {
        int index = 0;
        @Override
        public void run() {

            TransitionDrawable td = new TransitionDrawable(new Drawable[]{
                    bitmapDrawables[index % bitmapDrawables.length],
                    bitmapDrawables[++index % bitmapDrawables.length]
            });
            td.setCrossFadeEnabled(true);
            imageView.setImageDrawable(td);

            td.startTransition(500); // transitionDurationMillis
            handler.postDelayed(this, 3000);
        }
    }, 3000);

}

On pause you should clean up the handler

handler.removeCallbacksAndMessages(null);
aaronvargas
  • 12,189
  • 3
  • 52
  • 52