使用上下滑动的动画显示和隐藏一个视图

384

我有一个LinearLayout,我想使用一个Animation将该布局向上或向下推动,以便在更改其可见性时显示或隐藏它。

我看过一些示例,但没有符合我的需求。

我已经为动画创建了两个xml文件,但不知道如何在更改LinearLayout的可见性时启动它们。

20个回答

725

在 Android 3.0(Honeycomb)中引入的新动画 API 可以非常简单地创建此类动画。

View 向下滑动一定距离:

view.animate().translationY(distance);

你可以通过以下方式将 View 滑回到初始位置:

view.animate().translationY(0);
你还可以轻松地组合多个动画。以下动画将同时使一个 View 向下滑动其高度并淡入:
// Prepare the View for the animation
view.setVisibility(View.VISIBLE);
view.setAlpha(0.0f);

// Start the animation
view.animate()
    .translationY(view.getHeight())
    .alpha(1.0f)
    .setListener(null);

接下来,您可以将View淡出并将其滑回到原始位置。 我们还设置了一个AnimatorListener,以便在动画完成后可以将View的可见性设置为GONE:

view.animate()
    .translationY(0)
    .alpha(0.0f)
    .setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            view.setVisibility(View.GONE);
        }
    });

2
为什么视图一旦消失就不可见了? - Ram
3
@Ram,当将视图的可见性设置为“View.GONE”时,您试图通过动画化视图实现什么目的?如果将其可见性设置为除了“View.VISIBLE”以外的任何值,则该视图将不可见。我不明白您在问什么。如果您希望您的动画可见,请勿将视图的可见性设置为“View.GONE”。 - Xaver Kapeller
2
面对和Ram一样的问题,第一次它运行得很好,但是从下一次开始,当我将该视图设置为gone状态并尝试再次使该视图可见时,它不会显示。 - Pankaj kumar
13
@XaverKapeller 我认为许多人遇到的问题是,对于多次发生的动画,监听器onAnimationEnd每次都会被调用,这意味着当视图显示时也会调用onAnimationEnd,从而将其可见性设置为Gone等。 - oldergod
1
当您想要再次将其显示出来时,请使用 setListener(null) 将其设置为可见。 - Shahab Rauf
显示剩余17条评论

187

我曾经遇到了理解和应用所接受的答案的困难,我需要更多的上下文。现在我已经弄清楚了,这里是一个完整的例子:

输入图像描述

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button myButton;
    View myView;
    boolean isUp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myView = findViewById(R.id.my_view);
        myButton = findViewById(R.id.my_button);

        // initialize as invisible (could also do in xml)
        myView.setVisibility(View.INVISIBLE);
        myButton.setText("Slide up");
        isUp = false;
    }

    // slide the view from below itself to the current position
    public void slideUp(View view){
        view.setVisibility(View.VISIBLE);
        TranslateAnimation animate = new TranslateAnimation(
                0,                 // fromXDelta
                0,                 // toXDelta
                view.getHeight(),  // fromYDelta
                0);                // toYDelta
        animate.setDuration(500);
        animate.setFillAfter(true);
        view.startAnimation(animate);
    }

    // slide the view from its current position to below itself
    public void slideDown(View view){
        TranslateAnimation animate = new TranslateAnimation(
                0,                 // fromXDelta
                0,                 // toXDelta
                0,                 // fromYDelta
                view.getHeight()); // toYDelta
        animate.setDuration(500);
        animate.setFillAfter(true);
        view.startAnimation(animate);
    }

    public void onSlideViewButtonClick(View view) {
        if (isUp) {
            slideDown(myView);
            myButton.setText("Slide up");
        } else {
            slideUp(myView);
            myButton.setText("Slide down");
        }
        isUp = !isUp;
    }
}

activity_mail.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.slideview.MainActivity">

    <Button
        android:id="@+id/my_button"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:onClick="onSlideViewButtonClick"
        android:layout_width="150dp"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:id="@+id/my_view"
        android:background="#a6e1aa"
        android:orientation="vertical"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="200dp">

    </LinearLayout>

</RelativeLayout>

注意事项

  • 感谢这篇文章指明了正确的方向,它比本页上的其他答案更有帮助。
  • 如果你想要在屏幕上显示该视图,则不要将其初始化为INVISIBLE
  • 由于我们将其完全动画化到屏幕之外,因此无需将其设置回INVISIBLE。但是,如果你没有完全将其动画化到屏幕之外,则可以添加一个 alpha 动画,并使用AnimatorListenerAdapter设置可见性。
  • 属性动画文档

android:visibility="invisible" 用于将视图动画初始化为隐藏状态。 - Goodlife
7
如果在滑动视图下有可点击的视图,我不建议使用animate.setFillAfter(true);,因为它将导致这些视图无法接收到事件。请注意不改变原意,使翻译更通俗易懂。 - HOCiNE BEKKOUCHE
2
请注意,如果没有使用.setVisibility(View.INVISIBLE);,则向上滑动功能在视觉上不会按预期工作。 - Advait Saravade
Translate Animation moves the view. If you want to animate view like its scaling itself then use ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1) - Zohab Ali
请注意,如果您的视图中存在可点击元素,则TranslateAnimation不会移动它们。它只会移动像素。在大多数情况下,这可能是意外的。请参考此链接以同时移动可点击元素:https://dev59.com/cmox5IYBdhLWcg3wpV3F#34631361 - Kaushal28

97

现在,视图可见性变化的动画应该通过Transition API来完成,该API在支持(androidx)软件包中提供。只需调用TransitionManager.beginDelayedTransition方法并使用Slide转换,然后改变视图的可见性。

输入图像描述

import androidx.transition.Slide;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;

private void toggle(boolean show) {
    View redLayout = findViewById(R.id.redLayout);
    ViewGroup parent = findViewById(R.id.parent);

    Transition transition = new Slide(Gravity.BOTTOM);
    transition.setDuration(600);
    transition.addTarget(R.id.redLayout);

    TransitionManager.beginDelayedTransition(parent, transition);
    redLayout.setVisibility(show ? View.VISIBLE : View.GONE);
}

主活动界面布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="play" />

    <LinearLayout
        android:id="@+id/redLayout"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="#5f00"
        android:layout_alignParentBottom="true" />
</RelativeLayout>

查看这个答案,其中包括另一个默认和自定义转换示例。


2
最好和最容易的答案之一!谢谢! - krisDrOid
1
请注意,这需要 minSdkVersion 21 - lasec0203
3
不需要,类来自于“androidx”包。它在21 api之前的版本上运行良好。 - ashakirov
1
这个解决方案存在问题。一些动画在API 29+上无法正常工作。 - DIRTY DAVE
点赞,这应该是被采纳的答案。 - Ashish Choudhary
显示剩余5条评论

52
最简单的解决方案是在包含视图的容器上设置android:animateLayoutChanges="true"
为了更好地理解:如果您有一个像下面这样的布局,该容器中所有视图的可见性更改都将自动进行动画处理。
<LinearLayout android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    >

    <Views_which_change_visibility>

</LinearLayout>

您可以在Android开发者网站上了解更多关于此的详细信息。


这是最简单的方法,但由于手机制造商和其代码更改,其行为可能会有所不同。 - b2mob
这会使alpha动画化,而不是位置。 - Suragch
是的,但如果我理解正确,这就是原始问题所涉及的内容。如果您想要动画位置,您可以使用一个使用具有稳定ID的ViewHolders的RecyclerView。 - Stefan Medack
如果在嵌套视图中无法正常工作,请参考此答案 https://dev59.com/DmIj5IYBdhLWcg3wk2A2#59649918 - Amr

23

Kotlin

根据Suragch答案,这里介绍一种使用View扩展的优雅方式:

fun View.slideUp(duration: Int = 500) {
    visibility = View.VISIBLE
    val animate = TranslateAnimation(0f, 0f, this.height.toFloat(), 0f)
    animate.duration = duration.toLong()
    animate.fillAfter = true
    this.startAnimation(animate)
}

fun View.slideDown(duration: Int = 500) {
    visibility = View.VISIBLE
    val animate = TranslateAnimation(0f, 0f, 0f, this.height.toFloat())
    animate.duration = duration.toLong()
    animate.fillAfter = true
    this.startAnimation(animate)
}

然后无论您想在哪里使用它,您只需使用 myView.slideUp()myView.slideDown()


唯一的问题是,它不需要“fillAfter=true”,因为它会阻止子视图的点击可访问性。 - Ranjan
您可能需要为slideDown动画添加一个监听器,并在onAnimationEnd时将视图设置为gone。 - Manohar

12

LinearLayout的可见性发生改变时,您可以通过创建LinearLayout的新子类并重写setVisibility()方法来启动正确的Animation。考虑实现以下内容:

public class SimpleViewAnimator extends LinearLayout
{
    private Animation inAnimation;
    private Animation outAnimation;

    public SimpleViewAnimator(Context context)
    {
        super(context);
    }

    public void setInAnimation(Animation inAnimation)
    {
        this.inAnimation = inAnimation;
    }

    public void setOutAnimation(Animation outAnimation)
    {
        this.outAnimation = outAnimation;
    }

    @Override
    public void setVisibility(int visibility)
    {
        if (getVisibility() != visibility)
        {
            if (visibility == VISIBLE)
            {
                if (inAnimation != null) startAnimation(inAnimation);
            }
            else if ((visibility == INVISIBLE) || (visibility == GONE))
            {
                if (outAnimation != null) startAnimation(outAnimation);
            }
        }

        super.setVisibility(visibility);
    }
}

1
我更喜欢子类的方法。非常感谢。 - MichelReap
1
当显示时,此方法有效;但当隐藏时,视图在动画可见之前就消失了。有什么解决办法吗? - Bernardo
12
这是一个可怕的解决方案。它使得View负责其自身的动画,这绝对不是你想要的。想象一下,如果你想在应用程序的另一个部分以不同的方式动画化View,那么该怎么办呢?添加一个标志以不自动动画化可见性?子类化View并覆盖setVisibility()以删除动画?甚至更糟的是,使用另一个动画实现setVisibility()?从那里开始,问题只会变得越来越丑陋。不要使用这个“解决方案”。 - Xaver Kapeller
3
更好地称之为AnimatedLinearLayout。 - Roel
1
@McAdam331 不是的。添加动画最简单的方法就是在你的布局xml中添加android:animateLayoutChanges="true"属性,然后Android会自动为你的出现和消失的视图添加动画效果。 - Xaver Kapeller
显示剩余2条评论

9
if (filter_section.getVisibility() == View.GONE) {
    filter_section.animate()
            .translationY(filter_section.getHeight()).alpha(1.0f)
            .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
                    filter_section.setVisibility(View.VISIBLE);
                    filter_section.setAlpha(0.0f);
                }
            });
} else {
    filter_section.animate()
            .translationY(0).alpha(0.0f)
            .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    filter_section.setVisibility(View.GONE);
                }
            });
}

11
这个回答存在的问题:1)可怕的代码格式。2)您使用代码片段发布代码,实际上无法在浏览器中运行。这不仅添加了两个无用的按钮,还破坏了语法高亮。3)它只是一些随机的代码转储,没有任何解释或目的。4)您正在执行动画时更改可见性。除了这是明显的代码气味之外,这也不会正常工作。更改可见性会启动新的布局过程,只有在该过程完成后,动画才实际具有值可以使用。这样的问题层出不穷...... - Xaver Kapeller
我已经编辑了你的答案以修复格式,并将代码片段转换为实际的代码块。但是你必须填写剩下的部分... - Xaver Kapeller
抱歉,朋友,我根据你的代码进行了修改,因为它对我来说效果不佳,我的代码可以正常工作。但是我同意在发布方式上需要进行更改。 - Ameen Maheen
@AmeenMaheen setAlpha 是用来做什么的? - IgorGanapolsky
@ Igor Ganapolsky,它用于实现透明效果,即产生淡出效果。 - Ameen Maheen

7
您可以在Android应用程序中使用以下代码向上或向下滑动任何视图或布局:

您可以使用以下代码在Android应用程序中向上或向下滑动任何视图或布局:

boolean isClicked = false;
LinearLayout mLayoutTab = (LinearLayout) findViewById(R.id.linearlayout);

if(isClicked) {
    isClicked = false;
    mLayoutTab.animate()
        .translationYBy(120)
        .translationY(0)     
        .setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
} else {
    isClicked = true;
    mLayoutTab.animate()
         .translationYBy(0)
         .translationY(120)
         .setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
}

120是什么?0是什么?如果我想硬编码,setDuration的单位是什么? - stanley santoso
1
这里的120和0是与Y轴相关的距离,如果您硬编码,则在大屏幕或平板电脑上会出现问题,因此您需要从string.xml值中为所有不同的设备设置值。持续时间是您想要显示布局动画的时间....!!!对我的糟糕英语感到抱歉...! - varotariya vajsi
@varotariyavajsi 这实际上并没有显示/隐藏视图的可见性。 - IgorGanapolsky
你好,Igor Ganapolsky。我知道这些...它只是在Y方向上进行视图转换,如果用户需要像底部滑块一样显示上下移动,那么它将正常工作。 - varotariya vajsi

6
使用 ObjectAnimator
private fun slideDown(view: View) {
    val height = view.height
    ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0f, height.toFloat()).apply {
        duration = 1000
        start()
    }
}

private fun slideUp(view: View) {
    val height = view.height
    ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, height.toFloat(), 0f)).apply {
        duration = 1000
        start()
    }
}

4
小改进:我们可以使用常量View.TRANSLATION_Y代替"translationY",并且在向上滑动的ObjectAnimation中,我们可以使用.apply{ doOnEnd { view.visibility = View.GONE } .......}.start()。 - tashi
0.toFloat() can also just be 0f - Tyler

4

使用这个类:

public class ExpandCollapseExtention {

 public static void expand(View view) {
    view.setVisibility(View.VISIBLE);

    final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    view.measure(widthSpec, heightSpec);

    ValueAnimator mAnimator = slideAnimator(view, 0, view.getMeasuredHeight());
    mAnimator.start();
}


public static void collapse(final View view) {
    int finalHeight = view.getHeight();

    ValueAnimator mAnimator = slideAnimator(view, finalHeight, 0);

    mAnimator.addListener(new Animator.AnimatorListener() {

        @Override
        public void onAnimationEnd(Animator animator) {               
            view.setVisibility(View.GONE);
        }


        @Override
        public void onAnimationStart(Animator animation) {

        }


        @Override
        public void onAnimationCancel(Animator animation) {

        }


        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    });
    mAnimator.start();
}


private static ValueAnimator slideAnimator(final View v, int start, int end) {

    ValueAnimator animator = ValueAnimator.ofInt(start, end);

    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {

            int value = (Integer) valueAnimator.getAnimatedValue();
            ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
            layoutParams.height = value;
            v.setLayoutParams(layoutParams);
        }
    });
    return animator;
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接