我正在寻找一个完整的工作示例,我将其称为“Gmail三片段动画”场景。具体地说,我们希望从两个片段开始,如下所示:
在一些UI事件(例如,点击片段B中的某些内容)时,我们希望:
而且,在按下BACK按钮时,我们希望这组操作被反转。
现在,我已经看到了很多部分实现;我将在下面回顾其中的四个。除了不完整之外,它们都有自己的问题。
@Reto Meier为同样的基本问题贡献了this popular answer,表明您将使用带有FragmentTransaction
的setCustomAnimations()
。对于两个片段的场景(例如,您最初只看到片段A,并希望使用动画效果将其替换为新的片段B),我完全同意。但是:
<objectAnimator>
在他的示例代码中使用以像素为单位的硬连接位置,考虑到不同的屏幕大小,这似乎是不切实际的,但是setCustomAnimations()
需要动画资源。排除了在JavaLinearLayout
中的android:layout_weight
之类的东西绑定在一起,GONE
?android:layout_weight
of 0
?预置动画到0的比例?还有别的吗?)@Roman Nurik指出,you can animate any property,包括你自己定义的那些。这可以帮助解决硬连接位置的问题,代价是创建自己的自定义布局管理器子类。这对一些人有帮助,但我仍然对Reto的其余解决方案感到困惑。
this pastebin entry的作者展示了一些诱人的伪代码,基本上是说所有三个片段最初都驻留在容器中,片段C在一开始就通过hide()
事务操作隐藏起来。然后,当UI事件发生时,我们show()
C和hide()
A。然而,我不明白这是如何处理B改变大小的事实的。它还依赖于这样一个事实,即您显然可以向同一容器添加多个片段,我不确定这是否是长期可靠的行为(更不用说它应该破坏findFragmentById()
,尽管我可以接受)。
this blog post的作者指出,Gmail根本没有使用setCustomAnimations()
,而是直接使用对象动画(“你只需改变根视图的左边距+改变右视图的宽度”)。然而,这仍然是一个两个片段的解决方案AFAICT,并且实现再次显示了以像素为单位的硬连接尺寸。
我将继续努力,所以有一天我可能会自己回答这个问题,但我真的希望有人已经为这个动画场景制定了三个片段的解决方案,并可以发布代码(或指向该代码的链接)。Android中的动画让我想要拔出我的头发,你们中看过我的人都知道这基本上是徒劳的努力。
发布于 2012-09-07 20:45:53
好的,这是我自己的解决方案,来自电子邮件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;
}
}
但是,在运行时,无论最初如何设置宽度,当您第一次使用hideLeft()
从显示问题的片段A和B切换到片段B和C时,宽度管理将由ThreePaneLayout
接管。在ThreePaneLayout
的术语中--与片段没有特定的联系--这三个片段是left
、middle
和right
。在调用hideLeft()
时,我们记录了left
和middle
的大小,并清除了这三个变量中任何一个上使用的权重,因此我们可以完全控制它们的大小。在hideLeft()
的时间点,我们将right
的大小设置为middle
的原始大小。
动画有两个方面:
ViewPropertyAnimator
将三个小部件转换为宽度为left
的左侧小部件,使用硬件层middleWidth
上的ObjectAnimator
将middle
宽度从初始宽度更改为原始宽度(对于所有这些,使用AnimatorSet
和ObjectAnimators
可能是一个更好的主意,尽管目前这是可行的)
(也可能是middleWidth
ObjectAnimator
否定了硬件层的价值,因为这需要相当连续的无效)
(绝对有可能我在动画理解上仍然有差距,而且我喜欢插入性的陈述)
最终的效果是left
滑出屏幕,middle
滑到left
的原始位置和大小,right
在middle
后面平移。
showLeft()
只是简单地颠倒了这个过程,使用相同的动画师组合,只是方向颠倒了。
该活动使用一个ThreePaneLayout
来保存一对ListFragment
小部件和一个Button
。选择左侧片段中的内容将添加(或更新)中间片段的内容。选择中间片段中的内容将设置Button
的标题,并在ThreePaneLayout
上执行hideLeft()
。如果我们隐藏了左侧,则按BACK将执行showLeft()
;否则,BACK将退出活动。因为这不使用FragmentTransactions
来影响动画,所以我们不得不自己管理那个“后台堆栈”。
上面链接的项目使用了本地片段和本地动画框架。我有一个相同项目的另一个版本,它使用安卓支持片段backport和NineOldAndroids作为动画:
https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC
后端口在第一代Kindle Fire上运行良好,尽管考虑到较低的硬件规格和缺乏硬件加速支持,动画有点不稳定。在Nexus7和其他当前一代的平板电脑上,这两种实现似乎都很顺利。
我当然对如何改进这个解决方案的想法持开放态度,或者其他解决方案,这些解决方案提供了明显优于我在这里所做的事情(或者使用了@weakwire )。
再次感谢每一位做出贡献的人!
发布于 2012-09-07 08:38:31
我在github上上传了我的建议(适用于所有安卓版本,尽管强烈建议将视图硬件加速用于此类动画。对于非硬件加速设备,位图缓存实现应该更适合)
演示视频中带有动画的是Here (慢帧率造成的屏幕投射)。实际性能非常快)
使用:
layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView(); //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView(); //<---inflate FragmentC here
//Left Animation set
layout.startLeftAnimation();
//Right Animation set
layout.startRightAnimation();
//You can even set interpolators
解释
创建了一个新的自定义RelativeLayout(ThreeLayout)和2个自定义动画(MyScalAnimation、MyTranslateAnimation)
假设另一个可见视图具有weight=1
,ThreeLayout
会以参数的形式获取左窗格的权重。
因此,new ThreeLayout(context,3)
创建了一个包含3个子视图的新视图,其中的左窗格占整个屏幕的1/3。另一个视图占用所有可用空间。
它在运行时计算宽度,一个更安全的实现是在
()中第一次计算尺寸。而不是在post()中
缩放和平移动画实际上是调整和移动视图的大小,而不是伪缩放和移动。请注意,fillAfter(true)
并不在任何地方使用。
View2 is right_of View1
和
View3 is right_of View2
设置了这些规则后,RelativeLayout会处理其他所有事情。动画会改变margins
(移动时)和[width,height]
on scale (缩放)
要访问每个子级(以便可以使用片段对其进行膨胀),可以调用
public FrameLayout getLeftLayout() {}
public FrameLayout getMiddleLayout() {}
public FrameLayout getRightLayout() {}
下面是两个动画演示
Stage1
-输入屏幕-!-输出
View1_____View3_____
Stage2
--输出-!-输入屏幕
View1_____View3_____
发布于 2013-02-20 00:51:08
我们构建了一个名为PanesLibrary的库来解决这个问题。它甚至比以前提供的更灵活,因为:
你可以在这里查看:https://github.com/Mapsaurus/Android-PanesLibrary
这是一个演示:http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be
它基本上允许您轻松地添加任意数量的动态调整大小的窗格,并将片段附加到这些窗格。希望你觉得它有用!:)
https://stackoverflow.com/questions/12253965
复制相似问题