Android: Expand/collapse animation

后端 未结 30 2207
说谎
说谎 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:44
    /**
     * Animation that either expands or collapses a view by sliding it down to make
     * it visible. Or by sliding it up so it will hide. It will look like it slides
     * behind the view above.
     * 
     */
    public class FinalExpandCollapseAnimation extends Animation
    {
        private View mAnimatedView;
        private int mEndHeight;
        private int mType;
        public final static int COLLAPSE = 1;
        public final static int EXPAND = 0;
        private LinearLayout.LayoutParams mLayoutParams;
        private RelativeLayout.LayoutParams mLayoutParamsRel;
        private String layout;
        private Context context;
    
        /**
         * Initializes expand collapse animation, has two types, collapse (1) and
         * expand (0).
         * 
         * @param view
         *            The view to animate
         * @param type
         *            The type of animation: 0 will expand from gone and 0 size to
         *            visible and layout size defined in xml. 1 will collapse view
         *            and set to gone
         */
        public FinalExpandCollapseAnimation(View view, int type, int height, String layout, Context context)
        {
            this.layout = layout;
            this.context = context;
            mAnimatedView = view;
            mEndHeight = mAnimatedView.getMeasuredHeight();
            if (layout.equalsIgnoreCase("linear"))
                mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
            else
                mLayoutParamsRel = ((RelativeLayout.LayoutParams) view.getLayoutParams());
            mType = type;
            if (mType == EXPAND)
            {
                AppConstant.ANIMATED_VIEW_HEIGHT = height;
            }
            else
            {
                if (layout.equalsIgnoreCase("linear"))
                    mLayoutParams.topMargin = 0;
                else
                    mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
            }
            setDuration(600);
        }
    
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t)
        {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f)
            {
                if (mType == EXPAND)
                {
                    if (layout.equalsIgnoreCase("linear"))
                    {
                        mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT
                                + (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
                    }
                    else
                    {
                        mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT
                                + (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
                    }
                    mAnimatedView.setVisibility(View.VISIBLE);
                }
                else
                {
                    if (layout.equalsIgnoreCase("linear"))
                        mLayoutParams.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
                    else
                        mLayoutParamsRel.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
                }
                mAnimatedView.requestLayout();
            }
            else
            {
                if (mType == EXPAND)
                {
                    if (layout.equalsIgnoreCase("linear"))
                    {
                        mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT;
                        mLayoutParams.topMargin = 0;
                    }
                    else
                    {
                        mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT;
                        mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
                    }
                    mAnimatedView.setVisibility(View.VISIBLE);
                    mAnimatedView.requestLayout();
                }
                else
                {
                    if (layout.equalsIgnoreCase("linear"))
                        mLayoutParams.height = 0;
                    else
                        mLayoutParamsRel.height = 0;
                    mAnimatedView.setVisibility(View.GONE);
                    mAnimatedView.requestLayout();
                }
            }
        }
    
        private int convertPixelsIntoDensityPixels(int pixels)
        {
            DisplayMetrics metrics = context.getResources().getDisplayMetrics();
            return (int) metrics.density * pixels;
        }
    }
    

    The class can be called in following way

       if (findViewById(R.id.ll_specailoffer_show_hide).getVisibility() == View.VISIBLE) {
                            ((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown_up);
    
                            FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
                                    findViewById(R.id.ll_specailoffer_show_hide),
                                    FinalExpandCollapseAnimation.COLLAPSE,
                                    SpecialOfferHeight, "linear", this);
                            findViewById(R.id.ll_specailoffer_show_hide)
                                    .startAnimation(finalExpandCollapseAnimation);
                            ((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
                        } else {
                            ((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown);
    
                            FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
                                    findViewById(R.id.ll_specailoffer_show_hide),
                                    FinalExpandCollapseAnimation.EXPAND,
                                    SpecialOfferHeight, "linear", this);
                            findViewById(R.id.ll_specailoffer_show_hide)
                                    .startAnimation(finalExpandCollapseAnimation);
                            ((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
                        }
    
    0 讨论(0)
  • 2020-11-22 05:45

    Yes, I agreed with the above comments. And indeed, it does seem like the right (or at least the easiest?) thing to do is to specify (in XML) an initial layout height of "0px" -- and then you can pass in another argument for "toHeight" (i.e. the "final height") to the constructor of your custom Animation sub-class, e.g. in the example above, it would look something like so:

        public DropDownAnim( View v, int toHeight ) { ... }
    

    Anyways, hope that helps! :)

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

    I was trying to do what I believe was a very similar animation and found an elegant solution. This code assumes that you are always going from 0->h or h->0 (h being the maximum height). The three constructor parameters are view = the view to be animated (in my case, a webview), targetHeight = the maximum height of the view, and down = a boolean which specifies the direction (true = expanding, false = collapsing).

    public class DropDownAnim extends Animation {
        private final int targetHeight;
        private final View view;
        private final boolean down;
    
        public DropDownAnim(View view, int targetHeight, boolean down) {
            this.view = view;
            this.targetHeight = targetHeight;
            this.down = down;
        }
    
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            int newHeight;
            if (down) {
                newHeight = (int) (targetHeight * interpolatedTime);
            } else {
                newHeight = (int) (targetHeight * (1 - interpolatedTime));
            }
            view.getLayoutParams().height = newHeight;
            view.requestLayout();
        }
    
        @Override
        public void initialize(int width, int height, int parentWidth,
                int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
        }
    
        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:46

    Here is my solution. I think it is simpler. It only expands the view but can easy be extended.

    public class WidthExpandAnimation extends Animation
    {
        int _targetWidth;
        View _view;
    
        public WidthExpandAnimation(View view)
        {
            _view = view;
        }
    
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t)
        {
            if (interpolatedTime < 1.f)
            {
                int newWidth = (int) (_targetWidth * interpolatedTime);
    
                _view.layout(_view.getLeft(), _view.getTop(),
                        _view.getLeft() + newWidth, _view.getBottom());
            }
            else
                _view.requestLayout();
        }
    
        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight)
        {
            super.initialize(width, height, parentWidth, parentHeight);
    
            _targetWidth = width;
        }
    
        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:46

    This is a proper working solution, I have tested it:

    Exapnd:

    private void expand(View v) {
        v.setVisibility(View.VISIBLE);
    
        v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    
        final int targetHeight = v.getMeasuredHeight();
    
        mAnimator = slideAnimator(0, targetHeight);
        mAnimator.setDuration(800);
        mAnimator.start();
    }
    

    Collapse:

    private void collapse(View v) {
        int finalHeight = v.getHeight();
    
        mAnimator = slideAnimator(finalHeight, 0);
    
        mAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
    
            }
    
            @Override
            public void onAnimationEnd(Animator animator) {
                //Height=0, but it set visibility to GONE
                llDescp.setVisibility(View.GONE);
            }
    
            @Override
            public void onAnimationCancel(Animator animator) {
    
            }
    
            @Override
            public void onAnimationRepeat(Animator animator) {
    
            }
        });
        mAnimator.start();
    }
    

    Value Animator:

    private ValueAnimator slideAnimator(int start, int end) {
        ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);
    
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //Update Height
                int value = (Integer) valueAnimator.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
                layoutParams.height = value;
                v.setLayoutParams(layoutParams);
            }
        });
        return mAnimator;
    }
    

    View v is the view to be animated, PARENT_VIEW is the container view containing the view.

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

    I would like to add something to the very helpful answer above. If you don't know the height you'll end up with since your views .getHeight() returns 0 you can do the following to get the height:

    contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
    int finalHeight = view.getMeasuredHeight();
    

    Where DUMMY_HIGH_DIMENSIONS is the width/height (in pixels) your view is constrained to ... having this a huge number is reasonable when the view is encapsulated with a ScrollView.

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