Android: Expand/collapse animation

后端 未结 30 2175
说谎
说谎 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:20

    Making use of Kotlin Extension Functions this is tested and shortest answer

    Just call animateVisibility(expand/collapse) on any View.

    fun View.animateVisibility(setVisible: Boolean) {
        if (setVisible) expand(this) else collapse(this)
    }
    
    private fun expand(view: View) {
        view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        val initialHeight = 0
        val targetHeight = view.measuredHeight
    
        // Older versions of Android (pre API 21) cancel animations for views with a height of 0.
        //v.getLayoutParams().height = 1;
        view.layoutParams.height = 0
        view.visibility = View.VISIBLE
    
        animateView(view, initialHeight, targetHeight)
    }
    
    private fun collapse(view: View) {
        val initialHeight = view.measuredHeight
        val targetHeight = 0
    
        animateView(view, initialHeight, targetHeight)
    }
    
    private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
        val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
        valueAnimator.addUpdateListener { animation ->
            v.layoutParams.height = animation.animatedValue as Int
            v.requestLayout()
        }
        valueAnimator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationEnd(animation: Animator) {
                v.layoutParams.height = targetHeight
            }
    
            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
        valueAnimator.duration = 300
        valueAnimator.interpolator = DecelerateInterpolator()
        valueAnimator.start()
    }
    
    0 讨论(0)
  • 2020-11-22 05:21

    Ok, I just found a VERY ugly solution :

    public static Animation expand(final View v, Runnable onEnd) {
        try {
            Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
            m.setAccessible(true);
            m.invoke(
                v,
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(((View)v.getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST)
            );
        } catch (Exception e){
            Log.e("test", "", e);
        }
        final int initialHeight = v.getMeasuredHeight();
        Log.d("test", "initialHeight="+initialHeight);
    
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        Animation a = new Animation()
        {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                final int newHeight = (int)(initialHeight * interpolatedTime);
                v.getLayoutParams().height = newHeight;
                v.requestLayout();
            }
    
            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };
        a.setDuration(5000);
        v.startAnimation(a);
        return a;
    }
    

    Feel free to propose a better solution !

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

    Adding to Tom Esterez's excellent answer and Erik B's excellent update to it, I thought I'd post my own take, compacting the expand and contract methods into one. This way, you could for example have an action like this...

    button.setOnClickListener(v -> expandCollapse(view));
    

    ... which calls the method below and letting it figure out what to do after each onClick()...

    public static void expandCollapse(View view) {
    
        boolean expand = view.getVisibility() == View.GONE;
        Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
    
        view.measure(
            View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        );
    
        int height = view.getMeasuredHeight();
        int duration = (int) (height/view.getContext().getResources().getDisplayMetrics().density);
    
        Animation animation = new Animation() {
            @Override protected void applyTransformation(float interpolatedTime, Transformation t) {
                if (expand) {
                    view.getLayoutParams().height = 1;
                    view.setVisibility(View.VISIBLE);
                    if (interpolatedTime == 1) {
                        view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                    } else {
                        view.getLayoutParams().height = (int) (height * interpolatedTime);
                    }
                    view.requestLayout();
                } else {
                    if (interpolatedTime == 1) {
                        view.setVisibility(View.GONE);
                    } else {
                        view.getLayoutParams().height = height - (int) (height * interpolatedTime);
                        view.requestLayout();
                    }
                }
            }
            @Override public boolean willChangeBounds() {
                return true;
            }
        };
    
        animation.setInterpolator(easeInOutQuart);
        animation.setDuration(duration);
        view.startAnimation(animation);
    
    }
    
    0 讨论(0)
  • 2020-11-22 05:23
    public static void slide(View v, int speed, int pos) {
        v.animate().setDuration(speed);
        v.animate().translationY(pos);
        v.animate().start();
    }
    
    // slide down
    slide(yourView, 250, yourViewHeight);
    // slide up
    slide(yourView, 250, 0);
    
    0 讨论(0)
  • 2020-11-22 05:24

    Based on solutions by @Tom Esterez and @Seth Nelson (top 2) I simlified them. As well as original solutions it doesn't depend on Developer options (animation settings).

    private void resizeWithAnimation(final View view, int duration, final int targetHeight) {
        final int initialHeight = view.getMeasuredHeight();
        final int distance = targetHeight - initialHeight;
    
        view.setVisibility(View.VISIBLE);
    
        Animation a = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if (interpolatedTime == 1 && targetHeight == 0) {
                    view.setVisibility(View.GONE);
                }
                view.getLayoutParams().height = (int) (initialHeight + distance * interpolatedTime);
                view.requestLayout();
            }
    
            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };
    
        a.setDuration(duration);
        view.startAnimation(a);
    }
    
    0 讨论(0)
  • 2020-11-22 05:25

    Use ValueAnimator:

    ValueAnimator expandAnimation = ValueAnimator.ofInt(mainView.getHeight(), 400);
    expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(final ValueAnimator animation) {
            int height = (Integer) animation.getAnimatedValue();
            RelativeLayout.LayoutParams lp = (LayoutParams) mainView.getLayoutParams();
            lp.height = height;
        }
    });
    
    
    expandAnimation.setDuration(500);
    expandAnimation.start();
    
    0 讨论(0)
提交回复
热议问题