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!
0 comments:
Post a Comment