动画视图移出我的布局

Animated Views move outside of my layout

我有一个 ScrollView,其中包含多个 RelativeLayoutsLinearLayouts,如下所示:

<ScrollView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true" >

      <RelativeLayout >

      </RelativeLayout>

      <LinearLayout >

      </LinearLayout>

</ScrollView>

现在,当我单击其中一个布局时,我希望它展开并显示更多信息。我设法通过使用 PropertyAnimator:

垂直缩放我想要的布局来做到这一点
relativeLayout.animate().scaleY(100).setDuration(duration).start();

同时,我使用另一个 PropertyAnimator 将任何 Views 移动到我垂直展开的那个下面,以便有足够的 space 用于展开的布局。到目前为止它正在工作。

不幸的是,Views 以某种方式移动到 ScrollView 的视口之外,所以我无法向下滚动并查看那些 Views 中的信息。本质上,这些视图的垂直平移使它们的下半部分无法到达,因为视口也不会扩展。

我在 ScrollView 上设置了 android:fillViewPort="true"。而且我还尝试使用 setFillViewPort() 以编程方式执行此操作,但都没有任何效果。

怎么了?为什么它不起作用?

来自 Android 开发者网站: ScrollView 是一个 FrameLayout,这意味着您应该在其中放置一个 child 包含要滚动的全部内容;这个 child 本身可能是一个具有复杂层次结构 objects 的布局管理器。经常使用的 child 是垂直方向的 LinearLayout,呈现一个垂直排列的 top-level 项目,用户可以滚动浏览。

我看到您在滚动视图中添加了多个布局 child,请添加一个线性布局并在该 LinearLayout 中添加其余布局。 希望能解决你的问题

试试这个。

<ScrollView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true" >

      <LinearLayout
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >


                <RelativeLayout >

                     </RelativeLayout>

                <LinearLayout >

                     </LinearLayout>

      </LinearLayout>

</ScrollView>

Scrollview 必须只有一个 child。所以我只创建了一个 LinearLayout child 并在其中添加了您的其余代码。

当您在 Views 上执行翻译动画时,那些 Views 不会真正在布局内移动。它只是用户的视觉效果,但在布局 and/or 测量时,任何翻译值都将被忽略。总是好像 Views 根本没有被翻译。

我猜你现在正在做的是:

  • 你对点击事件做出反应,展开你想展开的View
  • 您计算其他 View 需要移动多少才能容纳展开的 View
  • 然后你对那些 Views 执行移动动画。
  • 然后结果突然有一些 Views 移出屏幕。

这种方法实际上永远行不通。您始终需要记住,布局中的 Views 位置 只是 由布局确定。您所有的翻译基本上只是为了展示。所以这就是当您尝试执行上述操作时实际发生的情况:

  • 您对点击事件做出反应并展开 View
  • 此扩展导致 Android 开始布局和测量过程。计算所有视图的位置和大小,并将它们以新大小定位在新位置。
  • 因为现在 Views 已经在扩展后它们将要位于的位置,您的翻译动画只是将 Views 进一步向下移动,超出了它们应该位于的位置。
  • 作为此操作的副作用,Views 似乎无缘无故地移出了屏幕parent。

那么你能做些什么呢?本质上,您需要以相反的方式解决这个问题。正如我上面提到的,Android 已经为您计算了所有 Views 的新大小和位置,您可以利用它来发挥自己的优势。

您的问题有两种基本解决方案。您可以让 Android 使用 LayoutTransitions 为您执行动画,或者您手动执行动画。这两种方式都使用一种叫做 ViewTreeObserver 的东西。它可用于监听布局变化或新绘图过程。

但最重要的是:ScrollView 应该只与一个 child 一起工作。因此,为防止将来出现任何错误或问题,请将所有项目放在 ScrollView 内的另一个垂直方向的 LinearLayout 中。

1) 使用 LayoutTransition

这只适用于 API 16 级及以上。 API 16 级以下的可见性动画和平移动画将自动处理,但要获得高度变化动画,您需要 API 级别 16。

我不得不提的一件重要事情是:

  1. LayoutTransition 为您动画变化。所以如果你使用它,你可以删除所有你自定义的动画。如果您保留自己的动画,您只会与 LayoutTransition 执行的动画发生冲突。
  2. 如果您不喜欢 LayoutTransition 执行的动画,您可以自定义它们!我将在下面进一步解释如何做到这一点。

我通常使用这样的辅助方法来设置 LayoutTransition

public static void animateLayoutChanges(ViewGroup container) {
    final LayoutTransition transition = new LayoutTransition();
    transition.setDuration(300);

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        transition.enableTransitionType(LayoutTransition.CHANGING);
        transition.enableTransitionType(LayoutTransition.APPEARING);
        transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
        transition.enableTransitionType(LayoutTransition.DISAPPEARING);
        transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
    }

    container.setLayoutTransition(transition);
}

这将在 API 级别 16 及以上启用所有可能的自动转换,并仅使用低于该级别的默认启用转换。像这样使用它:

AnimatorUtils.animateLayoutChanges(linearLayout);

如果您在 ScrollView 中的 LinearLayout 上调用此方法,那么对 LinearLayout 的 height/width/visibility 及其直接 children 的所有更改都将为你动画。项目 add/remove 动画也为您处理。

要启用调整大小动画等各种转换,您需要在代码中设置 LayoutTransitions,但您可以通过设置 属性 [来启用项目 add/remove 动画等基本转换=76=]

android:animateLayoutChanges="true"

在 xml 布局中的 ViewGroup 上。

关于 LayoutTransitions 的文档很少,但涵盖了基础知识 here

如果您愿意,可以为每个事件自定义动画,例如 adding/removing a View 或像这样更改 View 的某些内容:

// APPEARING handles items being added to the ViewGroup
transition.setAnimator(LayoutTransition.APPEARING, someAnimator);

// CHANGING handles among other things height or width changes of items in the ViewGroup
transition.setAnimator(LayoutTransition.CHANGING, someOtherAnimator);

这是一个 DevByte 视频,更详细地解释了 LayoutTransitions:

另请注意,当 parent 的高度发生变化时,容器视图基本上可以切断部分动画。这不会发生在您的情况下,因为您的 ScrollView 具有固定大小并且不会根据 ScrollView 内的 children 调整大小,但是如果您在 [= =41=] 和 wrap_content 那么你需要在你想要设置动画的 View 上面的所有容器上设置 android:clipChildren="false"。您也可以在代码中使用 setClipChildren()


2) 手动设置所有项目的动画。

这比使用LayoutTransition要困难得多,主要是因为你必须对布局和测量过程有很多了解,否则你会出问题。然而,一旦你掌握了它,你就可以执行所有操作各种自定义动画。

基本流程是这样的:

  1. 记录当前View状态。
  2. 将布局更改为动画结束后的状态
  3. 在 Android 完成布局和测量后,记录新值。
  4. 现在将 View 从它们的旧位置动画化到它们的新位置

这个过程的核心是监听视图层次结构的变化。这是使用 ViewTreeObserver 完成的。您可以使用多种可能的回调,例如 OnPreDrawListenerOnGlobalLayoutListener。通常你会像这样实现它们:

final Animator animator = setupAnimator();
animator.setTarget(view);

// Record the current state
animator.setupStartValues();

modifyChildrenOfLinearLayout();

linearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        // Remove the callback immediately we only need to catch it this one time.
        linearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

        // Record the new state
        animator.setupEndValues();

        // Start the animation
        animator.start();
    }
});

OnGlobalLayoutListener 更擅长捕获布局更改,因为它是在布局过程完成后调用的。 OnPreDrawListener 在绘制下一帧之前调用,但它们不能保证布局过程已经完成。但在实践中,这种差异可以忽略不计。更重要的是,在较旧的较慢的设备上,新状态下的布局可能会短暂闪烁,因为它们需要一些时间来处理每个步骤。您可以通过使用 OnPreDrawListener 并返回 false 一次来防止这种情况。由于 OnGlobalLayoutListener 也只能在较新的 API 级别上完全可用,因此在大多数情况下您应该使用 OnPreDrawListener.

如果 LayoutTransitions 不能为您的问题提供充分的解决方案,而您 have/want 手动实现动画比学习如何有效地执行动画更重要。可以看看LayoutTransitionhere的源码。 LayoutTransition 的实现本质上完全符合我在这里解释的内容,它是最佳实践实现。我经常发现自己通过查看 android.animation 包的源代码来学习有关如何有效制作动画的新知识,如果您想了解 Android 上的动画,我建议您也这样做!

您还可以观看一些 Android DevBytes 有关动画的视频,例如:

在此视频中,他解释了如何使用 OnPreDrawListener.

ListView 中的展开单元格设置动画

永远记住,布局引擎是您的朋友。不要试图重新发明轮子并手动完成布局过程已经为您完成的事情。永远不要在执行动画时调用 requestLayout()

只需将 android:clipChildren="false" 添加到您的父动画视图中,动画就会在视图外运行。