好的,这是我的解决方案,源自于电子邮件AOSP应用程序,基于@Christopher在问题评论中的建议。
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
的原始大小。
动画有两个方面:
- 使用
ViewPropertyAnimator
对三个小部件执行平移,向左移动left
的宽度,使用硬件层
- 使用
ObjectAnimator
在自定义的伪属性middleWidth
上,将middle
的宽度从其原始宽度更改为left
的原始宽度
(虽然现在这样做可以,但使用AnimatorSet
和ObjectAnimators
可能更好)
(middleWidth
的ObjectAnimator
可能会抵消硬件层的值,因为那需要相当连续的失效)
(我肯定还有一些动画理解上的漏洞,并且我喜欢用括号陈述)
最终效果是left
滑出屏幕,middle
滑到left
的原始位置和大小,right
紧随其后地向右平移。
showLeft()
只是简单地反转了这个过程,使用相同的动画器组合,只是方向相反。
该活动使用 ThreePaneLayout
来容纳一对 ListFragment
小部件和一个 Button
。在左侧片段中选择某些内容会添加(或更新)中间片段的内容。在中间片段中选择某些内容会设置 Button
的标题,并执行 ThreePaneLayout
上的 hideLeft()
。如果我们隐藏了左边,按 BACK 键将会执行 showLeft()
;否则,BACK 键将退出活动。由于这不使用 FragmentTransactions
来影响动画,因此我们必须自己管理“后退堆栈”。
上面链接的项目使用本机片段和本机动画框架。我还有另一个版本的同一项目,它使用 Android 支持片段后移和 NineOldAndroids 进行动画处理:
https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC
这个回溯在第一代Kindle Fire上运行良好,但由于硬件规格较低且缺乏硬件加速支持,动画有点卡顿。在Nexus 7和其他当前一代平板电脑上,两种实现都看起来很流畅。
我当然愿意接受改进这个解决方案的想法,或者提供比我做的(或@weakwire使用的)更明显的优势的其他解决方案。
再次感谢所有为此做出贡献的人!
LinearLayout
/权重方法。 "此外,是否允许Drawable缓存?(这意味着布局在动画期间无法更新)"-- ::耸肩:: 除了在Android的后续版本中可能使用硬件层之外,我没有太多考虑这个方面。 - CommonsWare