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

Ant*_*427 5 java android android-animation android-scrollview

我有一个ScrollView包含几个RelativeLayouts,LinearLayouts像这样:

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

现在,当我点击其中一个布局时,我希望它能够扩展并显示更多信息.我已经成功地通过以下方式缩放我想要的布局PropertyAnimator:

relativeLayout.animate().scaleY(100).setDuration(duration).start();
Run Code Online (Sandbox Code Playgroud)

同时,我使用另一个PropertyAnimator移动Views到我垂直扩展的那个下方,以便有足够的空间用于扩展布局.到目前为止它正在运作.

不幸的是,Views那个移动不知何故最终ScrollView落在了视口之外,所以我无法向下滚动并查看其中的信息Views.从本质上讲,这些视图的垂直平移使其下部无法访问,因为视口也不会扩展.

我已经开始android:fillViewPort="true"ScrollView.而且我也试图以编程方式进行,setFillViewPort()但都没有任何影响.

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

Xav*_*ler 6

当你执行翻译动画时,Views那些Views并不真正在布局内移动.它只是为用户提供视觉效果,但在布局和/或测量时,忽略任何翻译值.它总是好像Views根本没有翻译.

我猜你现在正在做的是:

  • 您对click事件做出反应并展开View您要扩展的内容.
  • 您可以计算其他视图需要移动多少才能适应扩展View.
  • 然后你Views需要移动它们来执行翻译动画.
  • 然后结果突然有一些Views移出屏幕.

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

  • 您对click事件做出反应并展开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可以自定义它们!我将在下面解释如何进一步做到这一点.

我通常使用这样的辅助方法来设置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);
}
Run Code Online (Sandbox Code Playgroud)

这将启用API Level 16及更高版本上的所有可能的自动转换,并且只使用下面默认启用的转换.只需像这样使用它:

AnimatorUtils.animateLayoutChanges(linearLayout);
Run Code Online (Sandbox Code Playgroud)

如果你LinearLayoutScrollView所有的高度/宽度/能见度的变化中调用此方法LinearLayout,它的直接子项将为你动画.还会为您处理项目添加/删除动画.

要启用各种过渡,例如调整动画大小,您需要设置内置LayoutTransitions代码,但是您可以通过设置属性来启用项目添加/删除动画等基本过渡

android:animateLayoutChanges="true"
Run Code Online (Sandbox Code Playgroud)

ViewGroup你的XML布局.

目前只有很少的文档LayoutTransitions,但这里介绍了基础知识.

如果您愿意,可以为每个事件自定义动画,例如添加/删除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);
Run Code Online (Sandbox Code Playgroud)

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

另请注意,当父级的高度发生变化时,容器视图可以基本上切断部分动画.这不会在你的情况发生,因为你ScrollView有一个固定的大小和大小不会根据里面的孩子ScrollView,但如果实施这样的事情在ViewGroupwrap_content那么你需要设置android:clipChildren="false"上述所有集装箱View你想送动画.您也可以setClipChildren()在代码中使用.


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

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

基本过程是这样的:

  1. 记录当前View状态.
  2. 在动画完成后将布局更改为状态
  3. Android完成后,布局和测量一切记录新值.
  4. 现在将Views从旧位置动画到新位置

此过程的核心是侦听视图层次结构中的更改.这是使用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();
    }
});
Run Code Online (Sandbox Code Playgroud)

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

如果LayoutTransitions没有为您的问题提供适当的解决方案,并且您希望手动实现动画,那么学习如何有效地执行动画非常重要.你可以看一下LayoutTransition 这里的源代码.实现LayoutTransition基本上完全是我在这里解释的,它是一个最佳实践实现.我经常发现自己正在查看android.animation软件包的源代码,以了解有关如何高效动画的新内容,如果你想了解Android上的动画,我建议你也这样做!

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

在这个视频中,他解释了如何ListView使用一个动画来扩展一个扩展的单元格OnPreDrawListener.


永远记住,Layouting Engine是你的朋友.不要试图重新发明轮子并手动完成布局过程已经为你做的事情.并且requestLayout()在执行动画时不要打电话!