首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >完整的Gmail三片段动画场景的工作示例?

完整的Gmail三片段动画场景的工作示例?
EN

Stack Overflow用户
提问于 2012-09-04 05:00:52
回答 6查看 29.9K关注 0票数 130

我正在寻找一个完整的工作示例,我将其称为“Gmail三片段动画”场景。具体地说,我们希望从两个片段开始,如下所示:

在一些UI事件(例如,点击片段B中的某些内容)时,我们希望:

  • 片段A滑出屏幕到左侧
  • 片段B滑到屏幕的左边缘并收缩以占据片段A腾出的斑点
  • 片段C从屏幕右侧滑入并占据片段B腾出的斑点

而且,在按下BACK按钮时,我们希望这组操作被反转。

现在,我已经看到了很多部分实现;我将在下面回顾其中的四个。除了不完整之外,它们都有自己的问题。

@Reto Meier为同样的基本问题贡献了this popular answer,表明您将使用带有FragmentTransactionsetCustomAnimations()。对于两个片段的场景(例如,您最初只看到片段A,并希望使用动画效果将其替换为新的片段B),我完全同意。但是:

  • 由于您只能指定一个" in“和一个"out”动画,我看不出您将如何处理三个片段的场景所需的所有不同动画。
  • <objectAnimator>在他的示例代码中使用以像素为单位的硬连接位置,考虑到不同的屏幕大小,这似乎是不切实际的,但是setCustomAnimations()需要动画资源。排除了在Java
  • 中定义这些东西的可能性,我不知所措的是,用于缩放的对象动画师如何与用于按百分比分配空间的LinearLayout中的android:layout_weight之类的东西绑定在一起,
  • 我不知所措,不知道片断C在一开始是如何处理的(GONEandroid: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中的动画让我想要拔出我的头发,你们中看过我的人都知道这基本上是徒劳的努力。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 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 --的宽度应该为零。

代码语言:javascript
复制
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的术语中--与片段没有特定的联系--这三个片段是leftmiddleright。在调用hideLeft()时,我们记录了leftmiddle的大小,并清除了这三个变量中任何一个上使用的权重,因此我们可以完全控制它们的大小。在hideLeft()的时间点,我们将right的大小设置为middle的原始大小。

动画有两个方面:

  • 使用ViewPropertyAnimator将三个小部件转换为宽度为left的左侧小部件,使用硬件层
  • 使用自定义伪属性middleWidth上的ObjectAnimatormiddle宽度从初始宽度更改为原始宽度

(对于所有这些,使用AnimatorSetObjectAnimators可能是一个更好的主意,尽管目前这是可行的)

(也可能是middleWidth ObjectAnimator否定了硬件层的价值,因为这需要相当连续的无效)

(绝对有可能我在动画理解上仍然有差距,而且我喜欢插入性的陈述)

最终的效果是left滑出屏幕,middle滑到left的原始位置和大小,rightmiddle后面平移。

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 )。

再次感谢每一位做出贡献的人!

票数 31
EN

Stack Overflow用户

发布于 2012-09-07 08:38:31

我在github上上传了我的建议(适用于所有安卓版本,尽管强烈建议将视图硬件加速用于此类动画。对于非硬件加速设备,位图缓存实现应该更适合)

演示视频中带有动画的是Here (慢帧率造成的屏幕投射)。实际性能非常快)

使用:

代码语言:javascript
复制
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=1ThreeLayout会以参数的形式获取左窗格的权重。

因此,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 (缩放)

要访问每个子级(以便可以使用片段对其进行膨胀),可以调用

代码语言:javascript
复制
public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

下面是两个动画演示

Stage1

-输入屏幕-!-输出

View1_____View3_____

Stage2

--输出-!-输入屏幕

View1_____View3_____

票数 67
EN

Stack Overflow用户

发布于 2013-02-20 00:51:08

我们构建了一个名为PanesLibrary的库来解决这个问题。它甚至比以前提供的更灵活,因为:

  • 每个窗格都可以动态调整大小
  • 它允许任意数量的窗格(而不仅仅是2个或3个)在方向更改时正确保留窗格内的
  • 片段。

你可以在这里查看:https://github.com/Mapsaurus/Android-PanesLibrary

这是一个演示:http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

它基本上允许您轻松地添加任意数量的动态调整大小的窗格,并将片段附加到这些窗格。希望你觉得它有用!:)

票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12253965

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档