Gmail三个片段动画场景的完整工作样例是什么?

130
TL;DR: 我正在寻找一个完整的、可用的样例,我将其称为“Gmail三段式动画”场景。具体而言,我们想要从两个片段开始,就像这样:

two fragments

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

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

并且,在按下“返回”按钮时,我们希望执行上述操作的顺序相反。

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


@Reto Meier为同一个基本问题贡献了这个受欢迎的答案,表明您将使用setCustomAnimations()FragmentTransaction。对于两个片段的情况(例如,您最初只看到片段A,想要使用动画效果替换为新的片段B),我完全同意。但是:

  • 由于只能指定一个“in”和一个“out”动画,我无法看到您如何处理三个片段场景所需的所有不同动画
  • 他的示例代码中的<objectAnimator>使用以像素为单位的硬编码位置,这似乎在考虑到不同屏幕尺寸时是不切实际的,然而setCustomAnimations()需要动画资源,排除了在Java中定义这些内容的可能性
  • 我不知道缩放的对象动画如何与按百分比分配空间的LinearLayout中的android:layout_weight之类的东西相结合
  • 我不知道Fragment C在最初如何处理(GONEandroid:layout_weight0?预先动画化为0的比例?还是其他什么?)

@Roman Nurik指出,您可以动画化任何属性,包括您自己定义的属性。这可以帮助解决硬编码位置的问题,但需要发明自己的自定义布局管理器子类。这对一些问题有所帮助,但我仍然对Reto的解决方案感到困惑。


这个pastebin条目的作者展示了一些诱人的伪代码,基本上说所有三个片段最初都存储在容器中,并通过hide()事务操作在开始时隐藏片段C。然后,在UI事件发生时,我们会show() C并hide() A。然而,我不明白它如何处理B改变大小的事实。它还依赖于一个事实,即您显然可以将多个片段添加到同一个容器中,我不确定这是否是长期可靠的行为(更不用说它应该会破坏findFragmentById(),尽管我可以接受这一点)。


这篇博客文章的作者指出,Gmail根本不使用setCustomAnimations(),而是直接使用对象动画(“你只需更改根视图的左边距+更改右视图的宽度”)。然而,据我所知,这仍然是一个双片段解决方案,并且所示的实现再次将尺寸硬编码为像素。


我会继续努力解决这个问题,所以有一天可能会自己回答,但我真的希望有人已经解决了这个动画场景的三段式解决方案,并可以发布代码(或链接)。 Android中的动画让我想把头发都拔光,而那些见过我的人都知道这是一个徒劳无功的努力。

2
这不是Romain Guy发布的类似这样的东西吗?http://www.curious-creature.org/2011/02/22/source-code-for-android-3-0-animation-demo/ - Fernando Gallego
1
@ferdy182:据我所知,他没有使用片段。从视觉上看,这是正确的基本效果,我可以将其作为尝试创建基于片段答案的另一个数据点。谢谢! - CommonsWare
4
电子邮件应用程序使用自定义的“ThreePaneLayout”,该布局可以动画化左侧视图的位置以及其他两个包含相关片段的视图的宽度/可见性。 - Christopher Orr
1
@weakwire: "你能告诉我如何测量片段A和B吗?" -- 你的任何一种方法都可以,但我稍微倾向于使用LinearLayout/权重方法。 "此外,是否允许Drawable缓存?(这意味着布局在动画期间无法更新)"-- ::耸肩:: 除了在Android的后续版本中可能使用硬件层之外,我没有太多考虑这个方面。 - CommonsWare
2
嘿@CommonsWare,我不会在这里打扰大家读完整个答案,但是有一个类似于你的问题,我给出了非常令人满意的答案。所以,建议你查看它(还要检查评论):https://dev59.com/Y2Yq5IYBdhLWcg3w-FTQ#14155204 - Budius
显示剩余5条评论
6个回答

67

我上传了我的提案,地址在GitHub。(尽管此动画适用于所有Android版本,但强烈建议使用视图硬件加速。对于未启用硬件加速的设备,位图缓存实现可能更合适)

带有该动画的演示视频在此处(由于屏幕录制而导致帧率较慢。实际性能非常快)。


用法:

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右侧

View3位于View2右侧
设置这些规则后,RelativeLayout会处理其他所有事情。动画改变边距(移动时)和缩放时的[width,height]。
要访问每个子项(以便您可以使用Fragment填充它),可以调用
public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

以下演示了2个动画


阶段1

--- 进入屏幕 ----------!----- 离开屏幕 ----

[View1] [_____View2_____] [_____View3_____]

阶段2

-- 离开屏幕 ! -------- 进入屏幕 ------

[View1] [View2] [_____View3_____]


1
@CommonsWare的scaleAnimation只是一个抽象名字。实际上没有缩放发生。它实际上调整视图的宽度。请检查源代码。LayoutResizer可能是更合适的名称。 - weakwire
1
啊,抱歉,我的错误。我在阅读您的评论时没有与您的来源进行交叉检查。谢谢!我想到了另一个解决方案,与您自己的开发并行,但目前您是“俱乐部的领袖”,可以获得赏金:https://dev59.com/yWct5IYBdhLWcg3wNq6v#12318422 - CommonsWare
2
你的视频显示了良好的帧率,但是你示例中使用的视图非常简单。在动画期间执行requestLayout()非常昂贵,特别是如果子项很复杂(例如ListView)。这可能会导致动画卡顿。GMail应用程序不调用requestLayout。相反,在动画开始之前,另一个较小的视图被放置在中间面板中。 - Thierry Roy
1
@weakwire,有没有可能创建一个从顶部出现的布局的方式? - meh
1
@meh 是的,我没有看到任何限制。只需使用RelativeLayout参数设置您的布局,稍后再设置边距即可。 - weakwire
显示剩余3条评论

31

好的,这是我的解决方案,源自于电子邮件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的术语中——它没有与片段相关的特定联系——三个部分是leftmiddleright。在调用hideLeft()时,我们记录leftmiddle的大小,并将任何三个部分上使用的权重清零,以便我们可以完全控制大小。在hideLeft()的时间点上,我们将right的大小设置为middle的原始大小。

动画有两个方面:

  • 使用ViewPropertyAnimator对三个小部件执行平移,向左移动left的宽度,使用硬件层
  • 使用ObjectAnimator在自定义的伪属性middleWidth上,将middle的宽度从其原始宽度更改为left的原始宽度

(虽然现在这样做可以,但使用AnimatorSetObjectAnimators可能更好)

middleWidthObjectAnimator可能会抵消硬件层的值,因为那需要相当连续的失效)

(我肯定还有一些动画理解上的漏洞,并且我喜欢用括号陈述)

最终效果是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使用的)更明显的优势的其他解决方案。
再次感谢所有为此做出贡献的人!

1
嗨!谢谢你的解决方案,但我认为添加v.setLayerType()有时会使事情变得有点慢。只需删除这些行(以及监听器),至少对于Android 3+设备来说是正确的。我刚在Galaxy Nexus上尝试了一下,没有这些行,它的表现要好得多。 - Eugene Nacu
1
@Andrew:"它有点延迟"——我不确定你在这里使用动词“lag”的意思。对我来说,“lag”意味着一个具体的比较对象。例如,与滑动运动相关联的动画可能会滞后于手指的移动。在这种情况下,一切都是由轻拍触发的,因此我不清楚动画可能滞后于什么。猜测也许“lags a bit”只是意味着“感觉缓慢”,我没有看到过这样的情况,但我也不声称自己有很强的审美感,所以对我而言可以接受的动画对你可能是不可接受的。 - CommonsWare
1
我并没有单独测试过你的项目,这只是我的实现。所以不用担心,如果你想听的话,很可能不是你的代码出了问题。 - Bogdan Zurac
2
@CommonsWare:我使用4.2编译了您的代码,做得很好。当我点击中间的ListView并快速按下返回按钮并重复此操作时,整个布局向右移动。它开始在左侧显示额外的空白列。这种行为是因为我不允许第一个动画(通过点击中间的ListView启动)完成并按下返回按钮。我通过在“translateWidgets”方法中用“translationX”替换“translationXBy”,并在“showLeft()”方法中将“translateWidgets(leftWidth,left,middle,right);”替换为“translateWidgets(0,left,middle,right);”来修复它。 - M-Wajeeh
2
我还在setMiddleWidth(int value);方法中将requestLayout();替换为middle.requestLayout();。我猜测这不会影响性能,因为GPU仍然会进行完整的布局传递,有任何评论吗? - M-Wajeeh
显示剩余9条评论

13
我们开发了一个名为PanesLibrary的库来解决这个问题。与先前提供的内容相比,它更加灵活,因为:
  • 每个窗格的大小可以动态调整
  • 它允许任意数量的窗格(不仅仅是2或3个)
  • 在方向改变时,窗格内的片段会被正确保留。
您可以在此处查看: https://github.com/Mapsaurus/Android-PanesLibrary 这里有一个演示:http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be 基本上,它允许您轻松添加任意数量的动态大小的窗格,并将片段附加到这些窗格中。希望您会发现它有用! :)

6
建立在你提供的一个示例 (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_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</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来实现动画效果(相反,它会对附加到片段的视图进行动画处理),因此您需要自己处理所有后退堆栈/片段事务,但与使动画运行良好的努力相比,这似乎不是一个坏的折衷方案 :)

其实际效果的低分辨率视频: http://youtu.be/Zm517j3bFCo


1
嗯,Gmail肯定会对我描述的Fragment A进行翻译。我担心将其重量减少到0可能会引入视觉伪影(例如,TextViewwrap_content在动画进行时垂直拉伸)。但是,也许通过动画调整重量就可以调整我所说的Fragment B的大小。谢谢! - CommonsWare
1
没问题!不过这是一个很好的观点,这可能会导致视觉伪影,具体取决于“片段A”中有哪些子视图。希望有人能找到更可靠的方法来解决它。 - DanielGrech

5

这不是使用片段。它是一个具有3个子元素的自定义布局。当您点击消息时,您可以使用offsetLeftAndRight()和动画器来偏移3个子元素。

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

它类似于Cyril Mottier的Fly-in应用程序菜单,但有3个元素而不是2个。

此外,第三个子元素的ViewPager是另一种表现形式:ViewPager通常使用片段(我知道它们不必须这样,但我从未见过其他实现方式),并且由于您无法在另一个Fragment中使用Fragments,因此这3个子元素可能不是片段....


我以前从未使用过“显示布局边界”--这对像这样的闭源应用程序非常有帮助(谢谢!)。看起来我所描述的 Fragment A 正在屏幕外移动(可以看到它的右侧边缘从右向左滑动),然后在我所描述的 Fragment B 下方弹出回来。那感觉像是一个TranslateAnimation, 虽然我确信有其他方法可以达到相同的效果。我需要进行一些实验。谢谢! - CommonsWare

2

我目前正在尝试做类似的事情,不同之处在于Fragment B会根据可用空间进行缩放,并且如果有足够的空间,三个面板可以同时打开。这是我的解决方案,但我不确定是否会坚持使用它。我希望有人能提供一个正确的答案。

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

所以,我动画化布局,没有使用片段事务。如果打开了片段C,我手动处理返回按钮来关闭它。

FragmentB使用layout_toLeftOf/ToRightOf来保持与Fragment A和C对齐。

当我的应用程序触发事件显示片段C时,我滑入片段C,并同时滑出片段A。(两个独立的动画)。反之,当Fragment A打开时,我同时关闭C。

在纵向模式或较小屏幕上,我使用略微不同的布局,并将Fragment C滑过屏幕。

要使用百分比设置Fragment 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_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接