动画视图移出我的布局
Animated Views move outside of my layout
我有一个 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>
现在,当我单击其中一个布局时,我希望它展开并显示更多信息。我设法通过使用 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。
我不得不提的一件重要事情是:
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 级别 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 视频,更详细地解释了 LayoutTransition
s:
另请注意,当 parent 的高度发生变化时,容器视图基本上可以切断部分动画。这不会发生在您的情况下,因为您的 ScrollView
具有固定大小并且不会根据 ScrollView
内的 children 调整大小,但是如果您在 [= =41=] 和 wrap_content
那么你需要在你想要设置动画的 View
上面的所有容器上设置 android:clipChildren="false"
。您也可以在代码中使用 setClipChildren()
。
2) 手动设置所有项目的动画。
这比使用LayoutTransition
要困难得多,主要是因为你必须对布局和测量过程有很多了解,否则你会出问题。然而,一旦你掌握了它,你就可以执行所有操作各种自定义动画。
基本流程是这样的:
- 记录当前
View
状态。
- 将布局更改为动画结束后的状态
- 在 Android 完成布局和测量后,记录新值。
- 现在将
View
从它们的旧位置动画化到它们的新位置
这个过程的核心是监听视图层次结构的变化。这是使用 ViewTreeObserver
完成的。您可以使用多种可能的回调,例如 OnPreDrawListener
或 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
一次来防止这种情况。由于 OnGlobalLayoutListener
也只能在较新的 API 级别上完全可用,因此在大多数情况下您应该使用 OnPreDrawListener
.
如果 LayoutTransitions
不能为您的问题提供充分的解决方案,而您 have/want 手动实现动画比学习如何有效地执行动画更重要。可以看看LayoutTransition
here的源码。 LayoutTransition
的实现本质上完全符合我在这里解释的内容,它是最佳实践实现。我经常发现自己通过查看 android.animation
包的源代码来学习有关如何有效制作动画的新知识,如果您想了解 Android 上的动画,我建议您也这样做!
您还可以观看一些 Android DevBytes 有关动画的视频,例如:
在此视频中,他解释了如何使用 OnPreDrawListener
.
为 ListView
中的展开单元格设置动画
永远记住,布局引擎是您的朋友。不要试图重新发明轮子并手动完成布局过程已经为您完成的事情。永远不要在执行动画时调用 requestLayout()
!
只需将 android:clipChildren="false"
添加到您的父动画视图中,动画就会在视图外运行。
我有一个 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>
现在,当我单击其中一个布局时,我希望它展开并显示更多信息。我设法通过使用 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。
我不得不提的一件重要事情是:
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 级别 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 视频,更详细地解释了 LayoutTransition
s:
另请注意,当 parent 的高度发生变化时,容器视图基本上可以切断部分动画。这不会发生在您的情况下,因为您的 ScrollView
具有固定大小并且不会根据 ScrollView
内的 children 调整大小,但是如果您在 [= =41=] 和 wrap_content
那么你需要在你想要设置动画的 View
上面的所有容器上设置 android:clipChildren="false"
。您也可以在代码中使用 setClipChildren()
。
2) 手动设置所有项目的动画。
这比使用LayoutTransition
要困难得多,主要是因为你必须对布局和测量过程有很多了解,否则你会出问题。然而,一旦你掌握了它,你就可以执行所有操作各种自定义动画。
基本流程是这样的:
- 记录当前
View
状态。 - 将布局更改为动画结束后的状态
- 在 Android 完成布局和测量后,记录新值。
- 现在将
View
从它们的旧位置动画化到它们的新位置
这个过程的核心是监听视图层次结构的变化。这是使用 ViewTreeObserver
完成的。您可以使用多种可能的回调,例如 OnPreDrawListener
或 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
一次来防止这种情况。由于 OnGlobalLayoutListener
也只能在较新的 API 级别上完全可用,因此在大多数情况下您应该使用 OnPreDrawListener
.
如果 LayoutTransitions
不能为您的问题提供充分的解决方案,而您 have/want 手动实现动画比学习如何有效地执行动画更重要。可以看看LayoutTransition
here的源码。 LayoutTransition
的实现本质上完全符合我在这里解释的内容,它是最佳实践实现。我经常发现自己通过查看 android.animation
包的源代码来学习有关如何有效制作动画的新知识,如果您想了解 Android 上的动画,我建议您也这样做!
您还可以观看一些 Android DevBytes 有关动画的视频,例如:
在此视频中,他解释了如何使用 OnPreDrawListener
.
ListView
中的展开单元格设置动画
永远记住,布局引擎是您的朋友。不要试图重新发明轮子并手动完成布局过程已经为您完成的事情。永远不要在执行动画时调用 requestLayout()
!
只需将 android:clipChildren="false"
添加到您的父动画视图中,动画就会在视图外运行。