Gmail 三片段动画场景的完整工作示例? [英] Complete Working Sample of the Gmail Three-Fragment Animation Scenario?

查看:23
本文介绍了Gmail 三片段动画场景的完整工作示例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL;DR:我正在寻找我将称之为Gmail 三片段动画"场景的完整工作示例.具体来说,我们要从两个片段开始,像这样:

TL;DR: I am looking for a complete working sample of what I'll refer to as "the Gmail three-fragment animation" scenario. Specifically, we want to start with two fragments, like this:

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

Upon some UI event (e.g., tapping on something in Fragment B), we want:

  • Fragment A 向左滑出屏幕
  • Fragment B 滑动到屏幕左边缘并缩小以占据 Fragment A 空出的位置
  • Fragment C 从屏幕右侧滑入并占据 Fragment B 空出的位置

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

And, on a BACK button press, we want that set of operations to be reversed.

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

Now, I have seen lots of partial implementations; I'll review four of them below. Beyond being incomplete, they all have their issues.

@Reto Meier 为相同的基本问题贡献了这个受欢迎的答案,表明您将使用 setCustomAnimations()FragmentTransaction.对于双片段场景(例如,您最初只看到片段 A,并希望使用动画效果将其替换为新的片段 B),我完全同意.然而:

@Reto Meier contributed this popular answer to the same basic question, indicating that you would use setCustomAnimations() with a FragmentTransaction. For a two-fragment scenario (e.g., you only see Fragment A initially, and want to replace it with a new Fragment B using animated effects), I am in complete agreement. However:

  • 由于您只能指定一个in"和一个out"动画,我看不出您将如何处理三片段场景所需的所有不同动画
  • 他的示例代码中的 使用以像素为单位的硬接线位置,鉴于不同的屏幕尺寸,这似乎不切实际,但 setCustomAnimations()需要动画资源,排除了在 Java 中定义这些东西的可能性
  • 我不知道用于缩放的对象动画师如何与 android:layout_weight 之类的东西在 LinearLayout 中结合以按百分比分配空间
  • 我不知道从一开始就如何处理片段 C (GONE? android:layout_weight of 0? pre-动画到 0 的比例?还有别的吗?)
  • Since you can only specify one "in" and one "out" animation, I can't see how you would handle all the different animations required for the three-fragment scenario
  • The <objectAnimator> in his sample code uses hard-wired positions in pixels, and that would seem to be impractical given varying screen sizes, yet setCustomAnimations() requires animation resources, precluding the possibility of defining these things in Java
  • I am at a loss as to how the object animators for scale tie in with things like android:layout_weight in a LinearLayout for allocating space on a percentage basis
  • I am at a loss as to how Fragment C is handled at the outset (GONE? android:layout_weight of 0? pre-animated to a scale of 0? something else?)

@Roman Nurik 指出您可以为任何属性设置动画,包括您自己定义的属性.这可以帮助解决硬接线位置的问题,代价是发明您自己的自定义布局管理器子类.这对一些人有帮助,但我仍然对 Reto 解决方案的其余部分感到困惑.

@Roman Nurik points out that you can animate any property, including ones that you define yourself. That can help solve the issue of the hard-wired positions, at the cost of inventing your own custom layout manager subclass. That helps some, but I'm still baffled by the rest of Reto's solution.

this pastebin entry的作者展示了一些诱人的伪代码,基本上是说所有三个片段都将驻留在容器中最初,通过 hide() 事务操作在一开始就隐藏了片段 C.然后我们在 UI 事件发生时 show() C 和 hide() A.但是,我看不出这如何处理 B 更改大小的事实.它还依赖于您显然可以将多个片段添加到同一个容器的事实,我不确定这是否是长期可靠的行为(更不用说它应该破坏 findFragmentById(),虽然我可以忍受).

The author of this pastebin entry shows some tantalizing pseudocode, basically saying that all three fragments would reside in the container initially, with Fragment C hidden at the outset via a hide() transaction operation. We then show() C and hide() A when the UI event occurs. However, I don't see how that handles the fact that B changes size. It also relies on the fact that you apparently can add multiple fragments to the same container, and I am not sure whether or not that is reliable behavior over the long term (not to mention it should break findFragmentById(), though I can live with that).

这篇博文的作者指出 Gmail 没有使用 setCustomAnimations() ,而是直接使用对象动画器(您只需更改根视图的左边距 + 更改右视图的宽度").然而,这仍然是一个两片段的解决方案 AFAICT,并且实现再次以像素为单位硬连线尺寸.

The author of this blog post indicates that Gmail is not using setCustomAnimations() at all, but instead directly uses object animators ("you just change left margin of the root view + change width of the right view"). However, this is still a two-fragment solution AFAICT, and the implementation shown once again hard-wires dimensions in pixels.

我会继续深入研究这个问题,所以有一天我可能会自己回答这个问题,但我真的希望有人已经为这个动画场景制定了三段解决方案,并且可以发布代码(或链接)).Android 中的动画让我很想拔头发,而那些看过我的人都知道,这在很大程度上是徒劳的.

I will continue plugging away at this, so I may wind up answering this myself someday, but I am really hoping that somebody has worked out the three-fragment solution for this animation scenario and can post the code (or a link thereto). Animations in Android make me want to pull my hair out, and those of you who have seen me know that this is a largely fruitless endeavor.

推荐答案

好的,这是我自己的解决方案,源自电子邮件 AOSP 应用程序,根据@Christopher 在问题评论中的建议.

OK, here is my own solution, derived from the Email AOSP app, per @Christopher's suggestion in the question's comments.

https://github.com/commonsguy/cw-omnibus/树/主/动画/ThreePane

@weakwire 的解决方案让我想起了我,尽管他使用经典的Animation 而不是动画师,并且他使用RelativeLayout 规则来强制定位.从赏金的角度来看,他很可能会得到赏金,除非其他有更巧妙解决方案的人发布了答案.

@weakwire's solution is reminiscent of mine, though he uses classic Animation rather than animators, and he uses RelativeLayout rules to enforce positioning. From the bounty standpoint, he will probably get the bounty, unless somebody else with a slicker solution yet posts an answer.

简而言之,该项目中的 ThreePaneLayout 是一个 LinearLayout 子类,旨在与三个孩子一起横向工作.这些孩子的宽度可以通过任何所需的方式在布局 XML 中设置——我展示了使用权重,但您可以通过维度资源或其他方式设置特定的宽度.第三个孩子——问题中的片段 C——的宽度应该为零.

In a nutshell, the ThreePaneLayout in that project is a LinearLayout subclass, designed to work in landscape with three children. Those childrens' widths can be set in the layout XML, via whatever desired means -- I show using weights, but you could have specific widths set by dimension resources or whatever. The third child -- Fragment C in the question -- should have a width of zero.

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()切换时,宽度管理由ThreePaneLayout接管展示了问题所指的片段 A 和 B 到片段 B 和 C.在 ThreePaneLayout 的术语中——它与片段没有特定的联系——三个部分是 leftmiddleright.在您调用 hideLeft() 时,我们会记录 leftmiddle 的大小,并将用于任何三,所以我们可以完全控制尺寸.在hideLeft()的时间点,我们将right的大小设置为middle的原始大小.

However, at runtime, no matter how you originally set up the widths, width management is taken over by ThreePaneLayout the first time you use hideLeft() to switch from showing what the question referred to as Fragments A and B to Fragments B and C. In the terminology of ThreePaneLayout -- which has no specific ties to fragments -- the three pieces are left, middle, and right. At the time you call hideLeft(), we record the sizes of left and middle and zero out any weights that were used on any of the three, so we can completely control the sizes. At the point in time of hideLeft(), we set the size of right to be the original size of middle.

动画有两方面:

  • 使用ViewPropertyAnimator 将三个小部件向左平移left 的宽度,使用硬件层
  • middleWidth 的自定义伪属性上使用 ObjectAnimatormiddle 宽度从它开始的任何宽度更改为原始宽度
  • Use a ViewPropertyAnimator to perform a translation of the three widgets to the left by the width of left, using a hardware layer
  • Use an ObjectAnimator on a custom pseudo-property of middleWidth to change the middle width from whatever it started with to the original width of left

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

(it is possible that it is a better idea to use an AnimatorSet and ObjectAnimators for all of these, though this works for now)

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

(it is also possible that the middleWidth ObjectAnimator negates the value of the hardware layer, since that requires fairly continuous invalidation)

(肯定可能我的动画理解仍然存在差距,而且我喜欢括号声明)

(it is definitely possible that I still have gaps in my animation comprehension, and that I like parenthetical statements)

最终效果是left滑出屏幕,middle滑动到left的原始位置和大小,right 翻译在 middle 后面.

The net effect is that left slides off the screen, middle slides to the original position and size of left, and right translates in right behind middle.

showLeft() 简单地颠倒过程,使用相同的动画师组合,只是方向相反.

showLeft() simply reverses the process, with the same mix of animators, just with the directions reversed.

Activity 使用一个 ThreePaneLayout 来保存一对 ListFragment 小部件和一个 Button.选择左侧片段中的某些内容会添加(或更新)中间片段的内容.在中间片段中选择某些内容设置 Button 的标题,并在 ThreePaneLayout 上执行 hideLeft().按BACK,如果我们隐藏左侧,将执行showLeft();否则,BACK 退出活动.由于这不使用 FragmentTransactions 来影响动画,我们只能自己管理后退堆栈".

The activity uses a ThreePaneLayout to hold a pair of ListFragment widgets and a Button. Selecting something in the left fragment adds (or updates the contents of) the middle fragment. Selecting something in the middle fragment sets the caption of the Button, plus executes hideLeft() on the ThreePaneLayout. Pressing BACK, if we hid the left side, will execute showLeft(); otherwise, BACK exits the activity. Since this does not use FragmentTransactions for affecting the animations, we are stuck managing that "back stack" ourselves.

上面链接的项目使用原生片段和原生动画框架.我有同一个项目的另一个版本,它使用 Android 支持片段向后移植和 NineOldAndroids 用于动画:

The project linked-to above uses native fragments and the native animator framework. I have another version of the same project that uses the Android Support fragments backport and NineOldAndroids for the animation:

https://github.com/commonsguy/cw-omnibus/树/主/动画/ThreePaneBC

backport 在第一代 Kindle Fire 上运行良好,但鉴于较低的硬件规格和缺乏硬件加速支持,动画有点生涩.这两种实现在 Nexus 7 和其他当前一代平板电脑上似乎都很顺利.

The backport works fine on a 1st generation Kindle Fire, though the animation is a bit jerky given the lower hardware specs and lack of hardware acceleration support. Both implementations seem smooth on a Nexus 7 and other current-generation tablets.

对于如何改进此解决方案或其他与我在这里所做的(或 @weakwire 使用的)相比具有明显优势的解决方案,我当然愿意接受.

I am certainly open for ideas of how to improve this solution, or other solutions that offer clear advantages over what I did here (or what @weakwire used).

再次感谢所有做出贡献的人!

Thanks again to everyone who has contributed!

这篇关于Gmail 三片段动画场景的完整工作示例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆