Monday, July 2, 2018

Apply animation sequentially to multiple views

Leave a Comment

I have an activity with 3 views (buttonViews) in a vertical linear layout. I am generating (inflating) these views dynamically. I want to apply an animation such that, on activity start, the first buttons slide in -> 100 ms delay -> second button slide in -> 100 ms delay -> Third button slide in.

Attempt

I tried implementing it in this way:

private void setMainButtons() {     ArrayList<String> dashboardTitles = DashboardUtils.getDashboardTitles();     ArrayList<Integer> dashboardIcons = DashboardUtils.getDashboardIcons();      final ViewGroup root = findViewById(R.id.button_container);      for (int i = 0; i < (dashboardTitles.size() < dashboardIcons.size() ? dashboardTitles.size() : dashboardIcons.size()); i++){         final View buttonView = DashboardButtonInflater.getDashboardButton(root, dashboardTitles.get(i), dashboardIcons.get(i), this);         if (buttonView == null) continue;         buttonView.setOnClickListener(this);         root.addView(buttonView);         animateBottomToTop(buttonView, (long) (i*50)); // Calling method to animate buttonView     } }  //The function that adds animation to buttonView, with a delay. private void animateBottomToTop(final View buttonView,long delay) {     AnimationSet animationSet = new AnimationSet(false);     animationSet.addAnimation(bottomToTop);     animationSet.addAnimation(fadeIn);     animationSet.setStartOffset(delay);     buttonView.setAnimation(animationSet); } 

Result:

The above method waits for the total delay of all the views and at the end, aminates all the views together. I can guess the culprit here is the thread. The dealy is actually stopping the UI thread from doing any animation. I could be wrong though.

I also tried running the animation code inside

new Thread(new Runnable(){...}).run() 

but that didn't work either.

Expectations:

Can somebody help me achieve the one-by-one animation on buttonView? Thank you.

4 Answers

Answers 1

Animations are statefull objects, you should not use the same instance multiple times simultaneously. In your case the bottomToTop and fadeIn animations are shared between the animation sets. When the set starts (initialize() is called) it will set the start offset of its children.

For example the method could look like :

//The function that adds animation to buttonView, with a delay. private void animateBottomToTop(final View buttonView,long delay) {     AnimationSet animationSet = new AnimationSet(false);     // create new instances of the animations each time     animationSet.addAnimation(createBottomToTop());     animationSet.addAnimation(createFadeIn());     animationSet.setStartOffset(delay);     buttonView.setAnimation(animationSet); } 

Answers 2

The problem might be easily solved with Transitions API. Having declared a root layout with this xml:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   android:id="@+id/content_frame"   android:layout_width="match_parent"   android:layout_height="match_parent"   android:orientation="vertical"/> 

Then inside activity:

class MainActivity : AppCompatActivity() {      lateinit var content: LinearLayout     private var counter = 0      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)          content = findViewById(R.id.content_frame)         // wait this view to be laid out and only then start adding and animating views         content.post { addNextChild() }     }      private fun addNextChild() {         // terminal condition         if (counter >= 3) return         ++counter          val button = createButton()         val slide = Slide()         slide.duration = 500         slide.startDelay = 100         slide.addListener(object : TransitionListenerAdapter() {             override fun onTransitionEnd(transition: Transition) {                 addNextChild()             }         })         TransitionManager.beginDelayedTransition(content, slide)         content.addView(button)     }      private fun createButton(): Button {         val button = Button(this)         button.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)         button.text = "button"         return button     }  } 

This chunk of code will result in following output:

You can adjust animation and delay times respectively.


If you want following behavior:

Then you can use following code:

class MainActivity : AppCompatActivity() {      lateinit var content: LinearLayout      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)          content = findViewById(R.id.content_frame)         content.post { addChildren() }     }      private fun addChildren() {         val button1 = createButton()         val button2 = createButton()         val button3 = createButton()          val slide1 = Slide()         slide1.duration = 500         slide1.addTarget(button1)          val slide2 = Slide()         slide2.duration = 500         slide2.startDelay = 150         slide2.addTarget(button2)          val slide3 = Slide()         slide3.duration = 500         slide3.startDelay = 300         slide3.addTarget(button3)          val set = TransitionSet()         set.addTransition(slide1)         set.addTransition(slide2)         set.addTransition(slide3)          TransitionManager.beginDelayedTransition(content, set)         content.addView(button1)         content.addView(button2)         content.addView(button3)     }      private fun createButton(): Button {         val button = Button(this)         button.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)         button.text = "button"         return button     } } 

Answers 3

Create method, which will accept Any number of Animation to invoke one after another. Just as example.

private void playOneAfterAnother(@NonNull Queue<Animation> anims) {      final Animation next = anims.poll();       /* You can set any other paramters,       like delay, for each next   Playing view, if any of course */       next.addListener(new AnimationListener() {             @Override             public void onAnimationEnd(Animator a) {                 if (!anim.isEmpty()) {                     playOneAfterAnother(anims);                 }             }             @Override             public void onAnimationStart(Animator a) {             }             @Override             public void onAnimationCancel(Animator a) {             }             @Override             public void onAnimationRepeat(Animator a) {             }         });      next.play(); } 

Or with delay for animations, it's easy too.

private void playOneAfterAnother(@NonNull Queue<Animation> anims,                    long offsetBetween, int nextIndex) {      final Animation next = anims.poll();       /* You can set any other paramters,       like delay, for each next   Playing view, if any of course */       next.setStartOffset(offsetBetween * nextIndex);      next.play();       if (!anim.isEmpty()) {          playOneAfterAnother(anims,                offsetBetween, nextIndex +1);      }  } 

Answers 4

Probably, what you need to use is AnimatorSet instead of AnimationSet. The AnimatorSet API allows you to choreograph animations in two ways: 1. PlaySequentially 2. PlayTogether using the apis:

AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(anim1, anim2, anim3, ...); animatorSet.playTogether(anim1, anim2, anim3, ...); 

You can further add delays to your animation using

animatorSet.setStartDelay(); 

Visit the complete API docs here https://developer.android.com/reference/android/animation/AnimatorSet

Hope this helps!

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment