当您对视图执行翻译动画时,这些视图实际上并没有在布局中移动,这只是对用户来说是视觉效果。但是,在布局和/或测量方面,任何翻译值都将被忽略。它总是好像视图根本没有被翻译一样。
我猜想您现在正在做的是:
1. 响应点击事件并展开要展开的视图。
2. 计算其他视图需要移动多少以适应扩展的视图。
3. 然后,您通过需要移动的距离执行翻译动画。
4. 结果,突然有几个视图移出屏幕。
这种方法实际上永远不会起作用。您始终需要记住,视图在布局中的位置仅由布局确定。所有您的翻译本质上只是为了展示。因此,当您尝试执行上述操作时,实际上正在发生以下情况:
1. 您响应点击事件并展开视图。
2. 此扩展导致Android启动布局和测量过程。计算所有视图的位置和大小,并将它们定位到其新位置并设置其新大小。
3. 由于现在视图已经位于扩展后的位置,您的翻译动画只会将视图向下移动,超出它们应该在的位置。
4. 作为此操作的副作用,视图似乎无缘无故地移出屏幕。
那么您该怎么做呢?实际上,您需要从另一个方面解决这个问题。如我上面提到的,Android已经为您计算了所有视图的新大小和位置,您可以利用它。
您的问题有两种基本解决方案。要么让Android使用LayoutTransitions为您执行动画,要么手动执行动画。这两种方法都使用称为ViewTreeObserver的东西。它可用于侦听布局更改或新绘制过程。
但首先:ScrollView只能与一个子项一起使用。因此,为了防止任何未来的错误或问题,请将ScrollView中的所有项目放在具有垂直方向的另一个LinearLayout内。
1)使用LayoutTransition
这仅适用于API级别16及以上。低于API级别16的可见性动画和翻译动画将自动处理,但要获得高度更改的动画效果,则需要具有API级别16。
我必须提醒您的一件重要事情是:
LayoutTransition
可以自动为你执行动画,因此如果你使用它,你可以删除所有自定义动画。如果你保留自己的动画,则会与 LayoutTransition
执行的动画发生冲突。
- 如果你不喜欢
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 Level 16及以上所有可能的自动转换,并仅在以下默认启用的转换中使用。只需像这样使用它:
AnimatorUtils.animateLayoutChanges(linearLayout)
如果您在
ScrollView
中调用此方法,则可以为
LinearLayout
及其直接子项的高度/宽度/可见性进行动画处理。此外,添加/删除项目的动画也会自动处理。
要启用各种过渡效果(如大小调整动画),您需要在代码中设置
LayoutTransitions
,但是您可以通过设置属性来启用基本过渡效果(如项目添加/删除动画)。
android:animateLayoutChanges="true"
在您的xml布局中的ViewGroup
上。
关于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)
以下是更详细讲解
LayoutTransition
的DevByte视频:
还要注意,当父容器的高度改变时,容器视图可以基本上剪切掉部分动画。由于您的ScrollView
具有固定大小并且不根据ScrollView
内的子项调整大小,因此不会发生这种情况,但是如果您在ViewGroup
中实现了类似的内容,则需要在所有包含要尝试动画化的View
上设置android:clipChildren =“false”
。您也可以使用代码中的setClipChildren()
。
2) 手动动画所有项。
与使用LayoutTransition
相比,这要困难得多,主要是因为您必须对布局和测量过程有很多了解,否则您将会引起问题。然而,一旦您掌握了它,就可以执行各种自定义动画。
基本过程如下:
- 记录当前
View
状态。
- 更改布局以在动画完成后达到的状态
- 在Android完成布局和测量所有内容后记录新值。
- 现在将
View
从其旧位置动画到新位置
这个过程的核心是监听视图层次结构中的更改。这是使用ViewTreeObserver
完成的。您可以使用多个可能的回调,例如OnPreDrawListener
或OnGlobalLayoutListener
。通常,您会像这样实现它们:
final Animator animator = setupAnimator();
animator.setTarget(view);
animator.setupStartValues();
modifyChildrenOfLinearLayout();
linearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
linearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
animator.setupEndValues();
animator.start();
}
});
OnGlobalLayoutListener
更适合捕获布局更改,因为它在布局过程完成后被调用。
OnPreDrawListener
在下一帧绘制之前被调用,但不能保证布局过程已经完成。但在实践中,这种差异是可以忽略的。更重要的是,在旧的较慢设备上可能会出现短暂的布局闪烁,因为它们需要一些时间来处理每个步骤。您可以使用
OnPreDrawListener
并仅返回
false
一次来防止这种情况发生。由于
OnGlobalLayoutListener
也仅在较新的API级别上完全可用,因此在大多数情况下应使用
OnPreDrawListener
。
如果
LayoutTransitions
无法为您提供适当的解决方案,并且您必须/想要手动实现动画,则学习如何有效地执行动画非常重要。您可以查看
LayoutTransition
的源代码
here。实际上,
LayoutTransition
的实现正是我一直在这里解释的内容,并且它是最佳实践的实现。我经常浏览
android.animation
包的源代码,以了解如何高效地进行动画,如果您想了解Android上的动画,则建议您也这样做!
您还可以观看一些有关动画的Android DevBytes视频,比如这个:
-
ListView Expanding Cells Animation
在这个视频中,他讲解了如何通过使用
OnPreDrawListener
来为
ListView
中的扩展单元格添加动画。
记住,布局引擎是您的朋友。不要试图重复造轮子并手动执行布局过程会为您执行的操作。在执行动画时永远不要调用
requestLayout()
!
OnPreDrawListener
并记录ScrollView
中所有视图的当前大小和位置,然后调整ScrollView
中的视图大小。OnPreDrawListener
将在下一帧准备好绘制时被调用,在这种情况下,将在调整大小启动的布局过程完成后调用。在OnPreDrawListener
中,记录ScrollView
中所有视图的新高度和位置,但返回 false,以便不会绘制新状态。 - Xaver KapellerOnPreDrawListener
。您只想捕获由视图调整大小引起的一个回调。 - Xaver KapellerOnPreDrawListener
中返回false来防止绘制新状态,然后将视图从旧位置动画到新位置,同时动画化高度/宽度的变化。诀窍在于Android为您计算所有值,而在执行动画时,所有视图都已正确调整大小并重新定位在布局中。 - Xaver Kapeller