好的,这是我自己的解决方案,根据@Christopher 在问题评论中的建议,源自电子邮件 AOSP 应用程序。
https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane
@weakwire 的解决方案让我想起了我,尽管他使用的是经典Animation
而不是动画师,并且他使用RelativeLayout
规则来强制定位。从赏金的角度来看,他可能会得到赏金,除非其他人有更巧妙的解决方案但发布了答案。
简而言之,该ThreePaneLayout
项目中的 是一个LinearLayout
子类,旨在与三个孩子一起在景观中工作。这些孩子的宽度可以通过任何所需的方式在布局 XML 中设置——我使用权重显示,但您可以通过维度资源或其他方式设置特定的宽度。第三个孩子——问题中的片段 C——的宽度应该为零。
package com.commonsware.android.anim.threepane;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
public class ThreePaneLayout extends LinearLayout {
private static final int ANIM_DURATION=500;
private View left=null;
private View middle=null;
private View right=null;
private int leftWidth=-1;
private int middleWidthNormal=-1;
public ThreePaneLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initSelf();
}
void initSelf() {
setOrientation(HORIZONTAL);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
left=getChildAt(0);
middle=getChildAt(1);
right=getChildAt(2);
}
public View getLeftView() {
return(left);
}
public View getMiddleView() {
return(middle);
}
public View getRightView() {
return(right);
}
public void hideLeft() {
if (leftWidth == -1) {
leftWidth=left.getWidth();
middleWidthNormal=middle.getWidth();
resetWidget(left, leftWidth);
resetWidget(middle, middleWidthNormal);
resetWidget(right, middleWidthNormal);
requestLayout();
}
translateWidgets(-1 * leftWidth, left, middle, right);
ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
leftWidth).setDuration(ANIM_DURATION).start();
}
public void showLeft() {
translateWidgets(leftWidth, left, middle, right);
ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
middleWidthNormal).setDuration(ANIM_DURATION)
.start();
}
public void setMiddleWidth(int value) {
middle.getLayoutParams().width=value;
requestLayout();
}
private void translateWidgets(int deltaX, View... views) {
for (final View v : views) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
v.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
}
}
private void resetWidget(View v, int width) {
LinearLayout.LayoutParams p=
(LinearLayout.LayoutParams)v.getLayoutParams();
p.width=width;
p.weight=0;
}
}
但是,在运行时,无论您最初如何设置宽度,宽度管理都会在ThreePaneLayout
您第一次用于hideLeft()
从显示问题称为片段 A 和 B 切换到片段 B 和 C 时接管。ThreePaneLayout
- 与片段没有特定联系 - 这三个部分是left
,middle
和right
. 在您致电 时hideLeft()
,我们会记录这三者中使用的任何权重的大小left
并将其归零,因此我们可以完全控制大小。middle
在 的时间点hideLeft()
,我们将 的大小设置right
为 的原始大小middle
。
动画分为两部分:
- 使用 a
ViewPropertyAnimator
将三个小部件向左平移 的宽度left
,使用硬件层
ObjectAnimator
在自定义伪属性上使用amiddleWidth
将middle
宽度从它开始的任何位置更改为原始宽度left
(可能对所有这些都使用AnimatorSet
and是一个更好的主意ObjectAnimators
,尽管这目前有效)
(也有可能middleWidth
ObjectAnimator
否定硬件层的值,因为这需要相当连续的失效)
(绝对有可能我对动画的理解仍然存在差距,而且我喜欢括号内的陈述)
最终效果是left
滑出屏幕,middle
滑到 的原始位置和大小left
,并right
在 的正后方平移middle
。
showLeft()
只需反转过程,使用相同的动画师组合,只是方向相反。
该活动使用 aThreePaneLayout
来保存一对ListFragment
小部件和一个Button
. 选择左侧片段中的某些内容会添加(或更新)中间片段的内容。选择中间片段中的内容设置 的标题Button
,并hideLeft()
在ThreePaneLayout
. 按 BACK,如果我们隐藏左侧,将执行showLeft()
;否则,BACK 退出活动。由于这不FragmentTransactions
用于影响动画,我们被困在自己管理那个“回栈”中。
上面链接到的项目使用原生片段和原生动画框架。我有另一个版本的同一项目,它使用 Android 支持片段 backport 和NineOldAndroids动画:
https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC
backport 在第一代 Kindle Fire 上运行良好,但由于硬件规格较低且缺乏硬件加速支持,动画有点生涩。在 Nexus 7 和其他当代平板电脑上,这两种实现似乎都很顺利。
我当然愿意接受有关如何改进此解决方案的想法,或者其他与我在这里所做的(或@weakwire 使用的)相比具有明显优势的解决方案。
再次感谢所有做出贡献的人!