动画视图移动到我的布局之外

时间:2015-03-20 14:30:59

标签: java android android-animation android-scrollview

我有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();

同时,我使用另一个PropertyAnimatorViews移动到垂直展开的Views之下,以便为展开的布局留出足够的空间。到目前为止它正在运作。

不幸的是,ScrollView以某种方式移动到Views的视口之外,所以我无法向下滚动并查看android:fillViewPort="true"中的信息。从本质上讲,这些视图的垂直平移使其下部无法访问,因为视口也不会扩展。

我在ScrollView上设置了setFillViewPort()。我也尝试用{{1}}以编程方式进行,但都没有任何效果。

出了什么问题?为什么不起作用?

4 个答案:

答案 0 :(得分:5)

当您在Views上执行翻译动画时,那些Views实际上并未在布局中移动。它只是为用户提供视觉效果,但在布局和/或测量时,忽略任何翻译值。始终好像Views根本没有翻译。

我猜你现在正在做的是:

  • 您对点击事件做出反应并展开您要展开的View
  • 您可以计算其他视图需要移动多少才能容纳展开的View
  • 然后,您需要移动那些Views上的翻译动画。
  • 然后突然有几个Views离开了屏幕。

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

  • 您对点击事件作出反应并展开View
  • 此扩展使Android开始布局和测量过程。计算所有视图的位置和大小,并将它们放置在新位置的新大小。
  • 由于现在Views已经位于扩展后的位置,因此翻译动画只会将Views进一步向下移动,超出它们应该的位置。
  • 作为一个副作用,Views似乎没有明显的理由离开屏幕。

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

您的问题有两种基本解决方案。您可以让Android使用LayoutTransitions为您执行动画,也可以手动执行动画。两种方式都使用称为ViewTreeObserver的东西。它可用于监听布局或新绘图过程中的更改。

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

1)使用LayoutTransition

这仅适用于API Level 16及更高版本。在API级别16下方,可以自动处理可见性动画和翻译动画,但要获得动画高度更改,您需要具有API级别16。

我必须提到的一件重要事情是:

  1. LayoutTransition为您设置动画效果。因此,如果您使用它,您可以删除所有自定义动画。如果您将自己的动画留在自己中,则只会与LayoutTransition执行的动画产生冲突。
  2. 如果您不喜欢LayoutTransition执行的动画,您可以自定义它们!我将在下面解释如何进一步做到这一点。
  3. 我通常使用这样的辅助方法来设置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 Level 16及更高版本上的所有可能的自动转换,并且只使用下面默认启用的转换。只需使用它:

    AnimatorUtils.animateLayoutChanges(linearLayout);
    

    如果您在LinearLayout的{​​{1}}上调用此方法,则ScrollView及其直接子项的高度/宽度/可见度的所有更改都将为您设置动画。还会为您处理项目添加/删除动画。

    要启用各种转换,例如调整动画大小,您需要在代码中设置LinearLayout,但您可以通过设置属性启用基本转换,例如项目添加/删除动画

    LayoutTransitions

    在xml布局中的android:animateLayoutChanges="true" 上。

    ViewGroup上只有最少的文档,但基本知识涵盖here

    如果您愿意,可以自定义每个事件的动画,例如添加/删除LayoutTransitions或更改View这样的内容:

    View

    这是一个DevByte视频,更详细地解释了// 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);

    还要注意,当父级的高度发生变化时,容器视图基本上可以切断部分动画。由于LayoutTransition具有固定大小并且未根据ScrollView内的子项调整大小,但您在ScrollView中实现了类似的内容,因此在您的情况下不会发生这种情况。 } ViewGroup然后您需要在您尝试设置动画的wrap_content上方的所有容器上设置android:clipChildren="false"。您也可以在代码中使用View


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

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

    基本过程是这样的:

    1. 记录当前LayoutTransition州。
    2. 在动画完成后将布局更改为状态
    3. 完成Android布局并测量一切后记录新值。
    4. 现在将View从旧位置动画到新位置
    5. 此过程的核心是侦听视图层次结构中的更改。这是使用View完成的。您可以使用多种可能的回调,例如ViewTreeObserverOnPreDrawListener。通常你会像这样实现它们:

      OnGlobalLayoutListener

      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也仅在较新的API级别上完全可用,因此在大多数情况下您应使用OnGlobalLayoutListener

      如果OnPreDrawListener没有为您的问题提供足够的解决方案并且您想要/想要手动实现动画,那么学习如何有效地执行动画非常重要。您可以查看LayoutTransitions here的源代码。 LayoutTransition的实现基本上完全符合我在此解释的内容,它是一种最佳实践实现。我经常发现自己正在查看LayoutTransition包的源代码,以了解有关如何有效制作动画的新内容,如果您想了解Android上的动画,我建议您也这样做!

      您还可以观看一些关于此动画的Android DevBytes视频:

      在此视频中,他解释了如何使用android.animationListView中的扩展单元设置动画。


      永远记住,Layouting Engine是你的朋友。不要尝试重新发明轮子并手动完成布局过程已经为你做的事情。并且在执行动画时不要致电OnPreDrawListener

答案 1 :(得分:0)

来自Android开发者网站: ScrollView是一个FrameLayout,意味着你应该在其中放置一个包含整个内容的子项进行滚动;这个子本身可能是一个具有复杂对象层次结构的布局管理器。经常使用的子项是垂直方向的LinearLayout,呈现用户可以滚动的顶级项目的垂直数组。

我可以看到您在Scroll View中添加了多个布局作为子项,请添加一个线性布局并在该LinearLayout中添加其余布局。 希望它能解决你的问题

答案 2 :(得分:0)

试试这个。

<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必须只有一个孩子。所以我只创建了一个LinearLayout子项并在其中添加了其余的代码。

答案 3 :(得分:0)

只需将android:clipChildren="false"添加到您的父动画视图中,动画就可以在视图外工作。