Android: Expand/collapse animation

后端 未结 30 2178
说谎
说谎 2020-11-22 05:01

Let\'s say I have a vertical linearLayout with :

[v1]
[v2]

By default v1 has visibily = GONE. I would like to show v1 with an expand animat

相关标签:
30条回答
  • 2020-11-22 05:26

    I think the easiest solution is to set android:animateLayoutChanges="true" to your LinearLayout and then just show/hide view by seting its visibility. Works like a charm, but you have no controll on the animation duration

    0 讨论(0)
  • 2020-11-22 05:27

    I created version in which you don't need to specify layout height, hence it's a lot easier and cleaner to use. The solution is to get the height in the first frame of the animation (it's available at that moment, at least during my tests). This way you can provide a View with an arbitrary height and bottom margin.

    There's also one little hack in the constructor - the bottom margin is set to -10000 so that the view stays hidden before the transformation (prevents flicker).

    public class ExpandAnimation extends Animation {
    
    
        private View mAnimatedView;
        private ViewGroup.MarginLayoutParams mViewLayoutParams;
        private int mMarginStart, mMarginEnd;
    
        public ExpandAnimation(View view) {
            mAnimatedView = view;
            mViewLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
            mMarginEnd = mViewLayoutParams.bottomMargin;
            mMarginStart = -10000; //hide before viewing by settings very high negative bottom margin (hack, but works nicely)
            mViewLayoutParams.bottomMargin = mMarginStart;
            mAnimatedView.setLayoutParams(mViewLayoutParams);
        }
    
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
                //view height is already known when the animation starts
                if(interpolatedTime==0){
                    mMarginStart = -mAnimatedView.getHeight();
                }
                mViewLayoutParams.bottomMargin = (int)((mMarginEnd-mMarginStart) * interpolatedTime)+mMarginStart;
                mAnimatedView.setLayoutParams(mViewLayoutParams);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:28

    @Tom Esterez's answer, but updated to use view.measure() properly per Android getMeasuredHeight returns wrong values !

        // http://easings.net/
        Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
    
        public static Animation expand(final View view) {
            int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
            int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            view.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
            final int targetHeight = view.getMeasuredHeight();
    
            // Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead.
            view.getLayoutParams().height = 1;
            view.setVisibility(View.VISIBLE);
    
            Animation animation = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
    
                   view.getLayoutParams().height = interpolatedTime == 1
                        ? ViewGroup.LayoutParams.WRAP_CONTENT
                        : (int) (targetHeight * interpolatedTime);
    
                view.requestLayout();
            }
    
                @Override
                public boolean willChangeBounds() {
                    return true;
                }
            };
    
            animation.setInterpolator(easeInOutQuart);
            animation.setDuration(computeDurationFromHeight(view));
            view.startAnimation(animation);
    
            return animation;
        }
    
        public static Animation collapse(final View view) {
            final int initialHeight = view.getMeasuredHeight();
    
            Animation a = new Animation() {
                @Override
                protected void applyTransformation(float interpolatedTime, Transformation t) {
                    if (interpolatedTime == 1) {
                        view.setVisibility(View.GONE);
                    } else {
                        view.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
                        view.requestLayout();
                    }
                }
    
                @Override
                public boolean willChangeBounds() {
                    return true;
                }
            };
    
            a.setInterpolator(easeInOutQuart);
    
            int durationMillis = computeDurationFromHeight(view);
            a.setDuration(durationMillis);
    
            view.startAnimation(a);
    
            return a;
        }
    
        private static int computeDurationFromHeight(View view) {
            // 1dp/ms * multiplier
            return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density);
        }
    
    0 讨论(0)
  • 2020-11-22 05:28

    You are on the right track. Make sure you have v1 set to have a layout height of zero right before the animation starts. You want to initialize your setup to look like the first frame of the animation before starting the animation.

    0 讨论(0)
  • 2020-11-22 05:29

    I adapted the currently accepted answer by Tom Esterez, which worked but had a choppy and not very smooth animation. My solution basically replaces the Animation with a ValueAnimator, which can be fitted with an Interpolator of your choice to achieve various effects such as overshoot, bounce, accelerate, etc.

    This solution works great with views that have a dynamic height (i.e. using WRAP_CONTENT), as it first measures the actual required height and then animates to that height.

    public static void expand(final View v) {
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        final int targetHeight = v.getMeasuredHeight();
    
        // Older versions of android (pre API 21) cancel animations for views with a height of 0.
        v.getLayoutParams().height = 1;
        v.setVisibility(View.VISIBLE);
    
        ValueAnimator va = ValueAnimator.ofInt(1, targetHeight);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
            }
    
            @Override public void onAnimationStart(Animator animation) {}
            @Override public void onAnimationCancel(Animator animation) {}
            @Override public void onAnimationRepeat(Animator animation) {}
        });
        va.setDuration(300);
        va.setInterpolator(new OvershootInterpolator());
        va.start();
    }
    
    public static void collapse(final View v) {
        final int initialHeight = v.getMeasuredHeight();
    
        ValueAnimator va = ValueAnimator.ofInt(initialHeight, 0);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                v.setVisibility(View.GONE);
            }
    
            @Override public void onAnimationStart(Animator animation) {}
            @Override public void onAnimationCancel(Animator animation) {}
            @Override public void onAnimationRepeat(Animator animation) {}
        });
        va.setDuration(300);
        va.setInterpolator(new DecelerateInterpolator());
        va.start();
    }
    

    You then simply call expand( myView ); or collapse( myView );.

    0 讨论(0)
  • 2020-11-22 05:30

    I took @LenaYan 's solution that didn't work properly to me (because it was transforming the View to a 0 height view before collapsing and/or expanding) and made some changes.

    Now it works great, by taking the View's previous height and start expanding with this size. Collapsing is the same.

    You can simply copy and paste the code below:

    public static void expand(final View v, int duration, int targetHeight) {
    
        int prevHeight  = v.getHeight();
    
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }
    
    public static void collapse(final View v, int duration, int targetHeight) {
        int prevHeight  = v.getHeight();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }
    

    Usage:

    //Expanding the View
       expand(yourView, 2000, 200);
    
    // Collapsing the View     
       collapse(yourView, 2000, 100);
    

    Easy enough!

    Thanks LenaYan for the initial code!

    0 讨论(0)
提交回复
热议问题