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

我有一个ScrollView ,它包含几个RelativeLayoutsLinearLayouts如下所示:

       

现在,当我点击其中一个布局时,我希望它能够扩展并显示更多信息。 我已经设法通过使用PropertyAnimator垂直缩放我想要的布局来做到这一点:

 relativeLayout.animate().scaleY(100).setDuration(duration).start(); 

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

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

我在ScrollView上设置了android:fillViewPort="true" 。 我也尝试用setFillViewPort()以编程方式进行,但都没有任何效果。

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

当您在Views上执行转换animation时,那些Views不会真正移动到布局中。 它只是用户的视觉效果,但在布局和/或测量时,忽略任何翻译值。 它总是好像根本没有翻译。

我猜你现在正在做的是:

  • 您对click事件做出反应并展开要扩展的View
  • 您可以计算其他视图需要移动多少以适应展开的View
  • 然后,您需要移动它们对这些Views执行翻译animation。
  • 然后结果突然有几个Views移出屏幕。

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

  • 您对click事件做出反应并展开View
  • 这种扩展使Android开始布局和测量过程。 计算所有视图的位置和大小,并将它们以新的大小定位在新位置。
  • 从现在开始, Views已经位于扩展后的位置,您可以将Views进一步向下移动,超出它们应该的位置。
  • 作为一个副作用, Views似乎没有明显的原因离开屏幕。

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

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

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

1)使用LayoutTransition

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

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

  1. LayoutTransition为您LayoutTransitionanimation更改animation。 因此,如果您使用它,您可以删除所有自定义animation。 如果您在自己的animation中留下自己的animation,则只会与LayoutTransition执行的animation产生冲突。
  2. 如果您不喜欢LayoutTransition执行的animation,您可以自定义它们! 我将在下面解释如何进一步做到这一点。

我通常使用这样的辅助方法来设置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); 

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

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

 android:animateLayoutChanges="true" 

在xml布局中的ViewGroup上。

LayoutTransitions上只有很少的文档,但这里介绍了基础知识。

如果您愿意,您可以自定义每个事件的animation,例如添加/删除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); 

这是一个DevBytevideo,它更详细地解释了LayoutTransition

  • LayoutTransitions可以轻松淡入/移动/调整animation大小

另请注意,当父级的高度发生更改时,容器视图可以基本上切断部分animation。 在你的情况下不会发生这种情况,因为你的ScrollView具有固定的大小并且没有根据ScrollView的子ScrollViewresize,但是如果你在带有wrap_contentViewGroup实现这样的东西,那么你需要设置android:clipChildren="false"View上方的所有容器上,您正试图制作animation。 您也可以在代码中使用setClipChildren()


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

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

基本过程是这样的:

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

此过程的核心是侦听视图层次结构中的更改。 这是使用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没有为您的问题提供足够的解决方案,并且您希望手动实现animation,那么学习如何有效地执行animation非常重要。 您可以在此处查看 LayoutTransition的源代码。 LayoutTransition的实现基本上完全是我在这里解释的,它是一个最佳实践实现。 我经常发现自己正在查看android.animation包的源代码,以了解有关如何高效animation的新内容,如果你想了解Android上的animation,我建议你也这样做!

您还可以观看一些有关此animation的Android DevBytesvideo:

  • ListView扩展单元格animation

在本video中,他解释了如何使用OnPreDrawListenerListView的扩展单元设置animation。


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

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

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

尝试这个。

         

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

只需将android:clipChildren="false"到父animation视图中,animation就可以在视图外工作。