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

Posted

技术标签:

【中文标题】Gmail 三段动画场景的完整工作示例?【英文标题】:Complete Working Sample of the Gmail Three-Fragment Animation Scenario? 【发布时间】:2012-08-28 13:13:49 【问题描述】:

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

对于一些 UI 事件(例如,点击片段 B 中的某物),我们希望:

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

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

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


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

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

【问题讨论】:

这不是 Romain Guy 发布的内容吗? curious-creature.org/2011/02/22/… @ferdy182:他没有使用碎片 AFAICT。从视觉上看,这是正确的基本效果,我也许可以将其用作另一个数据点来尝试创建基于片段的答案。谢谢! 想一想:标准的电子邮件应用程序在我的 Nexus 7 上具有类似(尽管不完全相同)的行为 — 这应该是开源的。 电子邮件应用程序使用自定义的“ThreePaneLayout”,它为左侧视图的位置以及其他两个视图的宽度/可见性设置动画——每个视图都包含相关的片段。 嘿@Co​​mmonsWare 我不会在这里用我的全部答案来打扰大家,但是有一个与你类似的问题,我给出了一个非常令人满意的答案。所以,只是建议你检查一下(也检查一下 cmets):***.com/a/14155204/906362 【参考方案1】:

将我的提案上传至github (适用于所有 android 版本,但强烈建议对此类动画使用视图硬件加速。对于非硬件加速设备,位图缓存实现应该更适合)

带有动画的演示视频是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)

ThreeLayout 获取左窗格的权重作为参数,假设另一个可见视图具有weight=1

所以new ThreeLayout(context,3) 创建了一个包含 3 个子项的新视图,左侧窗格占总屏幕的 1/3。另一个视图占据了所有可用空间。

它在运行时计算宽度,更安全的实现是在draw()中第一次计算尺寸。而不是在 post() 中

缩放和平移动画实际上是调整视图大小和移动视图,而不是伪 [scale,move]。请注意,fillAfter(true) 没有在任何地方使用。

View2 是 View1 的 right_of

View3 是 View2 的 right_of

设置了这些规则之后,RelativeLayout 会处理其他所有事情。动画会按比例改变margins(移动中)和[width,height]

要访问每个孩子(以便您可以使用 Fragment 对其进行充气,您可以调用

public FrameLayout getLeftLayout() 

public FrameLayout getMiddleLayout() 

public FrameLayout getRightLayout() 

下面是2个动画演示


第一阶段

---IN屏幕---------!-----OUT----

[视图1][_____视图2____][_____视图3_____]

第二阶段

--OUT-!--------IN 屏幕------

[视图1][视图2][_____视图3_____]

【讨论】:

虽然我还没有实现缩放动画师,但我担心它对于纯色场效果很好,而在我们可以说是“真实”内容的情况下效果较差。调整 Fragment B 的大小以适应 Fragment A 的空间的结果应该与最初将 Fragment B 放在那里的结果没有什么不同。例如,AFAIK,一个缩放动画师将缩放字体大小(通过将动画View 中的所有内容绘制得更小),但在 Gmail/Email 中,我们没有得到更小的字体,只是更少的单词。 @CommonsWare scaleAnimation 只是一个抽象名称。事实上,没有缩放发生。它实际上调整了视图的宽度。检查来源。 LayoutResizer 可能是一个更好的名字。 这不是我对View 上的scaleX 值的来源的解释,尽管我可能会误解。 @CommonsWare 检查这个github.com/weakwire/3PaneLayout/blob/master/src/com/example/…。我扩展 Animation 的唯一原因是访问 interpolatedTime 。 p.width = (int) 宽度;做了所有的魔法,它不是 scaleX。这是在指定时间内发生变化的实际视图尺寸。 您的视频显示了不错的帧率,但您的示例中使用的视图非常简单。在动画期间执行 requestLayout() 非常昂贵。特别是如果孩子很复杂(例如 ListView)。这可能会导致动画不连贯。 GMail 应用程序不调用 requestLayout。而是在动画开始之前将另一个较小的视图放入中间面板。【参考方案2】:

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

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

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


简而言之,该项目中的ThreePaneLayoutLinearLayout 子类,旨在与三个孩子一起在景观中工作。这些孩子的宽度可以通过任何所需的方式在布局 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() 时由 ThreePaneLayout 接管,以从显示问题称为片段 A 和 B 的内容切换到片段 B 和 C。在 ThreePaneLayout 的术语中(与片段没有特定联系),这三个片段是 leftmiddleright。在您致电hideLeft() 时,我们会记录leftmiddle 的大小,并将这三个中任何一个上使用的权重归零,因此我们可以完全控制大小。在hideLeft()的时间点,我们将right的大小设置为middle的原始大小。

动画分为两部分:

使用ViewPropertyAnimator 将三个小部件向左平移left 的宽度,使用硬件层 在middleWidth 的自定义伪属性上使用ObjectAnimatormiddle 的宽度从它开始的任何位置更改为left 的原始宽度

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

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

绝对可能我在动画理解方面仍有差距,而且我喜欢括号内的陈述)

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

showLeft() 只是颠倒了这个过程,使用相同的动画师组合,只是方向颠倒了。

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

上面链接到的项目使用原生片段和原生动画框架。我有同一个项目的另一个版本,它使用 Android 支持片段 backport 和 NineOldAndroids 来制作动画:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

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

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

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

【讨论】:

嗨!感谢您的解决方案,但我认为,添加 v.setLayerType() 有时会使事情变慢一些。只需删除这些行(以及 Listener)对于我刚刚在 Galaxy Nexus 上尝试过的 Android 3+ 设备来说至少是这样,如果没有这些行,它的工作效果会更好 “在 Nexus 7 和其他当代平板电脑上,这两种实现看起来都很流畅。”我相信你;但我正在 Nexus 7 上使用本机 ObjectAnimator 测试您的实现,但它有点滞后。最可能的原因是什么?我已经在 AndroidMANifest 中有 hardwareAccelerated:true 了。我真的不知道可能是什么问题。我也尝试过 DanielGrech 的变体,它的滞后性相同。我可以看到动画的差异(他的动画基于权重),但我不明白为什么在这两种情况下都会滞后。 @Andrew:“有点滞后”——我不确定您在这里如何使用动词“滞后”。对我来说,“滞后”意味着一个具体的比较目标。因此,例如,与滑动动作相关的动画可能会落后于手指运动。在这种情况下,一切都是由点击触发的,所以我不清楚动画可能落后于什么。猜测也许“有点滞后”只是意味着“感觉迟钝”,我没有看到,但我并不声称有很强的审美意识,所以我认为可以接受的动画可能对你来说是不可接受的。跨度> @CommonsWare:我用 4.2 编译了你的代码,干得好。当我点击中间ListView 并快速按下后退按钮并重复时,整个布局向右漂移。它开始在左侧显示额外的空间列。引入此行为是因为我不会让第一个动画(通过点击中间 ListView 开始)完成并按下后退按钮。我通过在translateWidgets 方法中将translationXBy 替换为translationX 并在showLeft() 方法中将translateWidgets(leftWidth, left, middle, right); 替换为translateWidgets(0, left, middle, right); 来修复它。 我还在setMiddleWidth(int value); 方法中将requestLayout(); 替换为middle.requestLayout();。它可能不会影响性能,因为我猜 GPU 仍然会进行完整的布局传递,任何 cmets?【参考方案3】:

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

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

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

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

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

【讨论】:

【参考方案4】:

以您链接到的示例之一 (http://android.amberfog.com/?p=758) 为基础,为 layout_weight 属性设置动画效果如何?这样,您可以将 3 个片段的重量变化动画在一起,并且您会获得它们都很好地滑动在一起的奖励:

从简单的布局开始。由于我们要为layout_weight 制作动画,我们需要一个LinearLayout 作为三个面板的根视图。:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_
android:layout_>

<LinearLayout
    android:id="@+id/panel1"
    android:layout_
    android:layout_weight="1"
    android:layout_/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_
    android:layout_weight="2"
    android:layout_/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_
    android:layout_weight="0"
    android:layout_/>
</LinearLayout>

然后是演示类:

public class DemoActivity extends Activity implements View.OnClickListener 
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    

    @Override
    public void onClick(View view) 
        toggleCollapseState();
    

    private void toggleCollapseState() 
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) 
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
         else 
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        
        isCollapsed = !isCollapsed;
    

    @Override
    public void onBackPressed() 
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) 
            toggleCollapseState();
         else 
            super.onBackPressed();
        
    

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    

    public void setPanel1Weight(float newWeight) 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    

    public float getPanel2Weight() 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    

    public void setPanel2Weight(float newWeight) 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    

    public float getPanel3Weight() 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    

    public void setPanel3Weight(float newWeight) 
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment 
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) 
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        
    

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment 
        private int mColor;

        public ColorFrag() 
            mColor = Color.BLUE; //Default
        

        public ColorFrag(int color) 
            mColor = color;
        

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        
    

此外,此示例不使用 FragmentTransactions 来实现动画(相反,它为片段附加到的视图设置动画),因此您需要自己完成所有 backstack/fragment 事务,但与获取的努力相比动画效果很好,这似乎不是一个糟糕的权衡:)

可怕的低分辨率视频:http://youtu.be/Zm517j3bFCo

【讨论】:

好吧,Gmail 肯定会翻译我所说的片段 A。我担心将其权重减少到 0 可能会引入视觉伪影(例如,TextViewwrap_content 垂直拉伸自身随着动画的进行)。但是,可能是动画重量是他们调整我所说的片段 B 的大小的方式。谢谢! 别担心!这是一个很好的观点,这可能会导致视觉伪影,具体取决于“片段 A”中的子视图。让我们希望有人找到更可靠的方法来做到这一点【参考方案5】:

这不是使用片段......这是一个自定义布局,有 3 个孩子。当你点击一条消息时,你会使用offsetLeftAndRight() 和一个动画师来抵消 3 个孩子。

JellyBean 中,您可以在“开发人员选项”设置中启用“显示布局边界”。幻灯片动画完成后,您仍然可以看到左侧菜单仍然存在,但位于中间面板下方。

它类似于Cyril Mottier's Fly-in app menu,但有 3 个元素而不是 2 个。

此外,第三个孩子的ViewPager 是这种行为的另一个迹象:ViewPager 通常使用片段(我知道他们不必这样做,但我从未见过除Fragment 之外的实现),并且由于您不能在另一个 Fragment 中使用 Fragments,因此 3 个孩子可能不是碎片......

【讨论】:

我以前从未使用过“显示布局边界”——这对于像这样的闭源应用程序非常有用(谢谢!)。看来我描述为片段 A 的内容正在屏幕外平移(您可以看到它的右边缘从右向左滑动),然后弹回到我描述为片段 B 的下方。虽然感觉就像 TranslateAnimation我确信有一些方法可以使用动画师来实现相同的目的。我将不得不对此进行一些实验。谢谢!【参考方案6】:

我目前正在尝试做类似的事情,除了 Fragment B 缩放以占用可用空间并且如果有足够的空间可以同时打开 3 窗格。到目前为止,这是我的解决方案,但我不确定我是否会坚持下去。我希望有人会提供显示正确方式的答案。

我没有使用 LinearLayout 并为权重设置动画,而是使用 RelativeLayout 并为边距设置动画。我不确定这是不是最好的方法,因为它需要在每次更新时调用 requestLayout()。不过在我所有的设备上都很流畅。

所以,我为布局设置了动画,我没有使用片段事务。如果片段 C 已打开,我会手动处理后退按钮以关闭片段 C。

FragmentB 使用 layout_toLeftOf/ToRightOf 使其与片段 A 和 C 对齐。

当我的应用触发事件显示片段 C 时,我同时滑入片段 C 和滑出片段 A。 (2个单独的动画)。相反,当 Fragment A 打开时,我同时关闭 C。

在纵向模式或较小的屏幕上,我使用稍微不同的布局并在屏幕上滑动片段 C。

要对片段 A 和 C 的宽度使用百分比,我认为您必须在运行时计算它...(?)

这是活动的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_
    android:layout_>

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_
        android:layout_
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_
        android:layout_
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_
        android:layout_
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

FragmentC 滑入或滑出的动画:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) 

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) 
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
     else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() 

        @Override
        public void onAnimationUpdate(ValueAnimator animation) 
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        
    );

    if (!slideIn) 
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() 

            @Override
            public void onAnimationEnd(Animator animation) 
                fragmentCRootView.setVisibility(View.GONE);
            
        );
    
    return anim;

【讨论】:

以上是关于Gmail 三段动画场景的完整工作示例?的主要内容,如果未能解决你的问题,请参考以下文章

尝试实现 NodeJS Gmail API 示例

看完48秒动画,让你不敢再登录HTTP网站(附完整示例代码)

高级PPT动画制作示例

插入更新和删除场景中触发器的完整示例

在 Gmail 中撰写邮件的 URL(具有完整的 Gmail 界面并指定收件人、密件抄送、主题等)

ThreeJS之动画交互逻辑及特效