Android:展开/折叠动画

Tom*_*rez 435 animation android

假设我有一个垂直linearLayout:

[v1]
[v2]
Run Code Online (Sandbox Code Playgroud)

默认情况下,v1具有visibily = GONE.我想用扩展动画展示v1并同时向下推v2.

我试过这样的事情:

Animation a = new Animation()
{
    int initialHeight;

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final int newHeight = (int)(initialHeight * interpolatedTime);
        v.getLayoutParams().height = newHeight;
        v.requestLayout();
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        initialHeight = height;
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
};
Run Code Online (Sandbox Code Playgroud)

但是使用这个解决方案,动画开始时我会闪烁.我认为这是由v1在应用动画之前显示完整大小引起的.

使用javascript,这是一行jQuery!用android做任何简单的方法吗?

Tom*_*rez 720

我看到这个问题变得流行,所以我发布了我的实际解决方案.主要优点是您不必知道应用动画的扩展高度,并且一旦展开视图,它会在内容更改时调整高度.这对我很有效.

public static void expand(final View v) {
    int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
    int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
    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);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targetHeight * interpolatedTime);
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // Expansion speed of 1dp/ms
    a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

public static void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // Collapse speed of 1dp/ms
    a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}
Run Code Online (Sandbox Code Playgroud)

  • v.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT,MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT,MeasureSpec.EXACTLY)); 在某些情况下(我的 - ListView),这种不匹配会导致错误的targtetHeight值 (13认同)
  • 什么是转型我在任何地方都找不到那种类型 (13认同)
  • @Tom Esterez这确实有效,但不是很顺利.还有其他工作可以顺利完成吗? (11认同)
  • @Alioo,导入android.view.animation.Transformation; (10认同)
  • @acntwww你可以得到一个平滑的动画,将持续时间乘以某个因子,如4.`a.setDuration(((int)(initialHeight/v.getContext().getResources().getDisplayMetrics().density))*4) ` (7认同)
  • 效果很好!我有测量高度的问题,因为我想扩展一个固定的dp元素,所以我把测量改为`v.measure(View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(0,View .MeasureSpec.UNSPECIFIED));`和`v.getLayoutParams().height = interpolatedTime == 1?targetHeight:(int)(targetHeight*interpolatedTime);`这对我有用! (5认同)
  • 虽然它有效,但并不顺利.调整持续时间并不能解决顺畅问题. (4认同)
  • 它非常适合布局,但不适用于其中的 RecyclerView :sweat_smile: (3认同)
  • @OferR在为孩子们使用View.GONE时,我没有看到任何改进.动画仍然很慢! (2认同)
  • expand()应该设置v.getLayoutParams()。height = 1;否则,Android的API21之前的版本将取消动画,并且视图将保持隐藏状态。 (2认同)

Set*_*son 140

我试图做我认为是一个非常相似的动画,并找到了一个优雅的解决方案.此代码假定您始终从0-> h或h-> 0(h是最大高度).三个构造函数参数是view =要动画的视图(在我的例子中是webview),targetHeight =视图的最大高度,down =一个指定方向的布尔值(true = expanded,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 代码中有一个拼写错误:"initalize"方法名称应该是"初始化"或者不会被调用.;)我建议将来使用@Override,这样这种拼写错误会被编译器捕获. (5认同)
  • Ops,让它工作,它的view.startAnimation(a)...性能不是很好,但它的工作:) (5认同)
  • 我正在做以下事情:"DropDownAnim anim = new DropDownAnim(grid_titulos_atual,GRID_HEIGHT,true); anim.setDuration(500); anim.start();" 但它不起作用.我在applyTransformation上放置了一些断点,但它们永远不会被触及 (4认同)
  • @IamStalker在这种情况下,你应该初始化两个变量,startHeight和endingHeight.然后改为:if(down){newHeight =(int)(((endingHeight-startingHeight)*interpolatedTime)+ startingHeight); } else {newHeight =(int)(((endingHeight-startingHeight)*(1 - interpolatedTime))+ startingHeight); } (3认同)
  • @Seth我认为newHeight可以简单地为(int)(((targetHeight -startingHeight)*interpolatedTime)+ startingHeight),无论方向如何,只要在initialize()中设置startingHeight即可. (3认同)

Mr.*_*.Fu 130

我今天偶然发现了同样的问题,我想这个问题的真正解决方案就是这个

<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
...
 />
Run Code Online (Sandbox Code Playgroud)

您必须为所有最顶层布局设置此属性,这些布局涉及移位.如果您现在将一个布局的可见性设置为GONE,另一个将占用空间,因为消失的布局将释放它.将会有一个默认动画,它是某种"淡出",但我认为你可以改变它 - 但是现在我还没有测试过的最后一个.

  • 动画布局更改:http://developer.android.com/training/animation/layout.html (8认同)
  • 这适用于扩展动画,但是对于折叠,动画在父布局缩小后发生. (4认同)
  • @shine_joseph是的,我在Recyclerview中使用它,当崩溃看起来很奇怪时:/ (3认同)

Ger*_*eto 63

我把@LenaYan的解决方案对我不起作用(因为它在折叠和/或扩展之前将View转换为0高度视图)并进行了一些更改.

现在,通过获取View的先前 高度并开始使用此大小进行扩展,效果很好.折叠是一样的.

您只需复制并粘贴以下代码即可:

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();
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

// Collapsing the View     
   collapse(yourView, 2000, 100);
Run Code Online (Sandbox Code Playgroud)

够容易!

感谢LenaYan的初始代码!


Chr*_*phK 39

另一种方法是使用具有以下缩放因子的缩放动画进行扩展:

ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1);
Run Code Online (Sandbox Code Playgroud)

和折叠:

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
Run Code Online (Sandbox Code Playgroud)

  • 这不会在动画期间推下它下方的视图,并且看起来好像是从0 - > h拉伸动画视图. (15认同)
  • 顺便说一句,查看动画非常适合缩放:oView.animate().scaleY(0)垂直折叠; 打开oView.animate().scaleY(1)(注意它只有sdk 12及以上版本). (5认同)
  • 你只需要给它一个持续时间......然后它就可以了 (3认同)

Eri*_*k B 26

@Tom Esterez的答案,但更新为正确使用view.measure()每个Android getMeasuredHeight返回错误的值!

    // 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);
    }
Run Code Online (Sandbox Code Playgroud)


Tom*_*rez 25

好的,我刚刚发现了一个非常难看的解决方案:

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;
}
Run Code Online (Sandbox Code Playgroud)

随意提出更好的解决方案!

  • +1,即使这被命名为丑陋,它适用于我们还不知道其大小的视图(例如,如果我们将新创建的视图(其大小为FILL_PARENT)添加到父级并且想要动画这个过程,包括动画父母大小的增长). (2认同)

Len*_*Yan 22

public static void expand(final View v, int duration, int targetHeight) {
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 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) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 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();
}
Run Code Online (Sandbox Code Playgroud)


Nir*_*ann 20

如果你不想一直扩展或折叠 - 这里是一个简单的高度动画 -

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

public class HeightAnimation extends Animation {
    protected final int originalHeight;
    protected final View view;
    protected float perValue;

    public HeightAnimation(View view, int fromHeight, int toHeight) {
        this.view = view;
        this.originalHeight = fromHeight;
        this.perValue = (toHeight - fromHeight);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
        view.requestLayout();
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

HeightAnimation heightAnim = new HeightAnimation(view, view.getHeight(), viewPager.getHeight() - otherView.getHeight());
heightAnim.setDuration(1000);
view.startAnimation(heightAnim);
Run Code Online (Sandbox Code Playgroud)


Raj*_*ran 14

使用 Kotlin 扩展函数这是经过测试的最短答案

只需在任何视图上调用 animateVisibility(expand/collapse)。

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()
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案,直到我意识到如果有一个多行的文本视图,其高度为wrap_content,当展开时,文本视图将只显示一行。我现在正在尝试修复 (2认同)

Mag*_*s W 9

我改编了汤姆埃斯特雷斯当前接受的答案,该答案有效,但动画不稳定且不太流畅.我的解决方案基本上取代了Animationa ValueAnimator,它可以配合Interpolator您的选择,以实现各种效果,如过冲,反弹,加速等.

此解决方案适用于具有动态高度(即使用WRAP_CONTENT)的视图,因为它首先测量实际所需的高度,然后设置为该高度的动画.

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();
}
Run Code Online (Sandbox Code Playgroud)

然后你只需打电话expand( myView );collapse( myView );.


gar*_*arh 6

我想在上面的非常有用的答案中添加一些内容.如果您不知道自从您的视图.getHeight()返回0后您将最终得到的高度,您可以执行以下操作来获得高度:

contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
int finalHeight = view.getMeasuredHeight();
Run Code Online (Sandbox Code Playgroud)

其中DUMMY_HIGH_DIMENSIONS是宽度/高度(以像素为单位),您的视图被约束为......当使用ScrollView封装视图时,这个数字很大是合理的.


小智 6

这是我用于通过动画调整视图宽度(LinearLayout)的片段.

代码应根据目标大小进行扩展或缩小.如果需要fill_parent宽度,则必须将父.getMeasuredWidth作为目标宽度传递,同时将标志设置为true.

希望它对你们有些帮助.

public class WidthResizeAnimation extends Animation {
int targetWidth;
int originaltWidth;
View view;
boolean expand;
int newWidth = 0;
boolean fillParent;

public WidthResizeAnimation(View view, int targetWidth, boolean fillParent) {
    this.view = view;
    this.originaltWidth = this.view.getMeasuredWidth();
    this.targetWidth = targetWidth;
    newWidth = originaltWidth;
    if (originaltWidth > targetWidth) {
        expand = false;
    } else {
        expand = true;
    }
    this.fillParent = fillParent;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    if (expand && newWidth < targetWidth) {
        newWidth = (int) (newWidth + (targetWidth - newWidth) * interpolatedTime);
    }

    if (!expand && newWidth > targetWidth) {
        newWidth = (int) (newWidth - (newWidth - targetWidth) * interpolatedTime);
    }
    if (fillParent && interpolatedTime == 1.0) {
        view.getLayoutParams().width = -1;

    } else {
        view.getLayoutParams().width = newWidth;
    }
    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;
}
Run Code Online (Sandbox Code Playgroud)

}


Ash*_*ini 6

对于平滑动画,请使用Handler with run method .....并享受展开/折叠动画

 class AnimUtils{

             public void expand(final View v) {
              int ANIMATION_DURATION=500;//in milisecond
    v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    final int targtetHeight = v.getMeasuredHeight();

    v.getLayoutParams().height = 0;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targtetHeight * interpolatedTime);
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    a.setDuration(ANIMATION_DURATION);

  // a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}



public void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    a.setDuration(ANIMATION_DURATION);
   // a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}
Run Code Online (Sandbox Code Playgroud)

}

并使用此代码调用:

       private void setAnimationOnView(final View inactive ) {
    //I am applying expand and collapse on this TextView ...You can use your view 

    //for expand animation
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

            new AnimationUtililty().expand(inactive);

        }
    }, 1000);


    //For collapse
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

            new AnimationUtililty().collapse(inactive);
            //inactive.setVisibility(View.GONE);

        }
    }, 8000);

}
Run Code Online (Sandbox Code Playgroud)

其他解决方案是:

               public void expandOrCollapse(final View v,String exp_or_colpse) {
    TranslateAnimation anim = null;
    if(exp_or_colpse.equals("expand"))
    {
        anim = new TranslateAnimation(0.0f, 0.0f, -v.getHeight(), 0.0f);
        v.setVisibility(View.VISIBLE);  
    }
    else{
        anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, -v.getHeight());
        AnimationListener collapselistener= new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            v.setVisibility(View.GONE);
            }
        };

        anim.setAnimationListener(collapselistener);
    }

     // To Collapse
        //

    anim.setDuration(300);
    anim.setInterpolator(new AccelerateInterpolator(0.5f));
    v.startAnimation(anim);
}
Run Code Online (Sandbox Code Playgroud)


mjp*_*p66 6

除了Tom Esterez的优秀答案和Erik B 对它的出色更新之外,我想我会发布自己的看法,将扩展和合同方法压缩成一个.这样,您可以举例如此...

button.setOnClickListener(v -> expandCollapse(view));
Run Code Online (Sandbox Code Playgroud)

...调用下面的方法并让它弄清楚每个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);

}
Run Code Online (Sandbox Code Playgroud)


小智 5

是的,我同意上面的评论。事实上,看起来正确的(或者至少是最简单的?)做法是(在 XML 中)指定“0px”的初始布局高度——然后你可以为“toHeight”传入另一个参数(即“最终高度”)到自定义动画子类的构造函数,例如在上面的示例中,它看起来像这样:

    public DropDownAnim( View v, int toHeight ) { ... }
Run Code Online (Sandbox Code Playgroud)

无论如何,希望有帮助!:)


小智 5

来自@Tom Esterez 和@Geraldo Neto 的组合解决方案

public static void expandOrCollapseView(View v,boolean expand){

    if(expand){
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        final int targetHeight = v.getMeasuredHeight();
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(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(500);
        valueAnimator.start();
    }
    else
    {
        final int initialHeight = v.getMeasuredHeight();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(initialHeight,0);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
                if((int)animation.getAnimatedValue() == 0)
                    v.setVisibility(View.GONE);
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(500);
        valueAnimator.start();
    }
}

//sample usage
expandOrCollapseView((Your ViewGroup),(Your ViewGroup).getVisibility()!=View.VISIBLE);
Run Code Online (Sandbox Code Playgroud)


Thr*_*ian 5

您可以使用TransitionAnimator更改要展开/折叠的部分的可见性,或ConstraintSet使用不同的布局。

在此输入图像描述

最简单的方法是使用具有 2 种不同布局和约束集的 MotionLayout,以便在单击按钮时从一种布局更改为另一种布局。您可以使用以下命令在布局之间进行更改

val constraintSet = ConstraintSet()
constraintSet.clone(this, R.layout.layout_collapsed)

val transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator(1.0f)
transition.setDuration(300)

TransitionManager.beginDelayedTransition(YOUR_VIEW, transition)
constraintSet.applyTo(YOUR_VIEW)
Run Code Online (Sandbox Code Playgroud)

使用转换api

旋转X.kt

我使用 Transitions api 创建了 gif 中的一个,可以更改rotationX。

class RotateX : Transition {

    @Keep
    constructor() : super()

    @Keep
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun getTransitionProperties(): Array<String> {
        return TRANSITION_PROPERTIES
    }

    override fun captureStartValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun captureEndValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun createAnimator(
        sceneRoot: ViewGroup,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {

        if (startValues == null || endValues == null) return null

        val startRotation = startValues.values[PROP_ROTATION] as Float
        val endRotation = endValues.values[PROP_ROTATION] as Float
        if (startRotation == endRotation) return null

        val view = endValues.view
        // ensure the pivot is set
        view.pivotX = view.width / 2f
        view.pivotY = view.height / 2f
        return ObjectAnimator.ofFloat(view, View.ROTATION_X, startRotation, endRotation)
    }

    private fun captureValues(transitionValues: TransitionValues) {
        val view = transitionValues.view
        if (view == null || view.width <= 0 || view.height <= 0) return
        transitionValues.values[PROP_ROTATION] = view.rotationX
    }

    companion object {

        private const val PROP_ROTATION = "iosched:rotate:rotation"
        private val TRANSITION_PROPERTIES = arrayOf(PROP_ROTATION)
    }
}
Run Code Online (Sandbox Code Playgroud)

创建针对展开按钮的 xml 文件

    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:interpolator/fast_out_slow_in">
    
        <transition class="com.smarttoolfactory.tutorial3_1transitions.transition.RotateX">
            <targets>
                <target android:targetId="@id/ivExpand" />
            </targets>
        </transition>
    
        <autoTransition android:duration="200" />
    
    </transitionSet>

My layout to be expanded or collapsed


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="4dp"
        android:layout_marginVertical="2dp"
        android:clickable="true"
        android:focusable="true"
        android:transitionName="@string/transition_card_view"
        app:cardCornerRadius="0dp"
        app:cardElevation="0dp"
        app:cardPreventCornerOverlap="false">


        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="16dp"
            android:paddingBottom="16dp">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/ivAvatar"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="8dp"
                android:scaleType="centerCrop"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:src="@drawable/avatar_1_raster" />

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/ivExpand"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:padding="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_baseline_expand_more_24" />

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:layout_marginTop="6dp"
                android:text="Some Title"
                android:textSize="20sp"
                android:textStyle="bold"
                app:layout_constraintStart_toEndOf="@+id/ivAvatar"
                app:layout_constraintTop_toTopOf="parent" />


            <TextView
                android:id="@+id/tvDate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:textColor="?android:textColorSecondary"
                android:textSize="12sp"
                app:layout_constraintStart_toStartOf="@+id/tvTitle"
                app:layout_constraintTop_toBottomOf="@id/tvTitle"
                tools:text="Tuesday 7pm" />

            <TextView
                android:id="@+id/tvBody"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="16dp"
                android:ellipsize="end"
                android:lines="1"
                android:text="@string/bacon_ipsum_short"
                android:textSize="16sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="@+id/ivAvatar"
                app:layout_constraintTop_toBottomOf="@id/tvDate" />


            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:orientation="horizontal"
                android:overScrollMode="never"
                android:visibility="gone"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tvBody"
                tools:listitem="@layout/item_image_destination" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </com.google.android.material.card.MaterialCardView>

</layout>
Run Code Online (Sandbox Code Playgroud)

并设置要折叠或展开的项目的可见性

private fun setUpExpandedStatus() {
    if (isExpanded) {
        binding.recyclerView.visibility = View.VISIBLE
        binding.ivExpand.rotationX = 180f
    } else {
        binding.recyclerView.visibility = View.GONE
        binding.ivExpand.rotationX = 0f
    }
}
Run Code Online (Sandbox Code Playgroud)

并开始过渡

   val transition = TransitionInflater.from(itemView.context)
            .inflateTransition(R.transition.icon_expand_toggle)

  TransitionManager.beginDelayedTransition(parent, transition)

  isExpanded = !isExpanded
        
  setUpExpandedStatus()
Run Code Online (Sandbox Code Playgroud)

我创建了动画和过渡示例,包括 gif 上的示例,您可以在那里查看它们。