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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12253965/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 10:12:49  来源:igfitidea点击:

Complete Working Sample of the Gmail Three-Fragment Animation Scenario?

androidandroid-fragmentsandroid-animation

提问by CommonsWare

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

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

two fragments

两个片段

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

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

  • Fragment A to slide off the screen to the left
  • Fragment B to slide to the left edge of the screen and shrink to take up the spot vacated by Fragment A
  • Fragment C to slide in from the right side of the screen and to take up the spot vacated by Fragment B
  • Fragment A 向左滑出屏幕
  • Fragment B 滑动到屏幕左边缘并缩小以占据 Fragment A 空出的位置
  • Fragment C从屏幕右侧滑入并占据Fragment B空出的位置

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

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

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 contributed this popular answerto 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:

@Reto迈尔促成这种普遍的回答相同的基本问题,表明你会使用setCustomAnimations()一个FragmentTransaction。对于两个片段的场景(例如,您最初只看到片段 A,并想使用动画效果将其替换为新的片段 B),我完全同意。然而:

  • 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_weightin a LinearLayoutfor allocating space on a percentage basis
  • I am at a loss as to how Fragment C is handled at the outset (GONE? android:layout_weightof 0? pre-animated to a scale of 0? something else?)
  • 由于您只能指定一个“in”和一个“out”动画,我看不出您将如何处理三片段场景所需的所有不同动画
  • <objectAnimator>他的代码示例使用像素的硬连线的位置,这似乎给出不同的屏幕尺寸是不切实际的,但setCustomAnimations()需要动画资源,排除在Java中定义这些事情的可能性
  • 我很茫然,以与事物对象的动画师规模的领带怎么样android:layout_weightLinearLayout对基于百分比分配空间
  • 我不知道片段 C 一开始是如何处理的(GONEandroid:layout_weight0?预动画到 0 的比例?还有什么?)


@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.

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



The author of this pastebin entryshows 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).

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



The author of this blog postindicates 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.

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



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.

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

采纳答案by CommonsWare

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

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

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

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

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

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



In a nutshell, the ThreePaneLayoutin that project is a LinearLayoutsubclass, 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.

简而言之,该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;
  }
}

However, at runtime, no matter how you originally set up the widths, width management is taken over by ThreePaneLayoutthe 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 leftand middleand 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 rightto be the original size of middle.

但是,在运行时,无论您最初如何设置宽度,宽度管理都会在ThreePaneLayout您第一次用于hideLeft()从显示问题所指的片段 A 和 B 切换到片段 B 和 C时接管。ThreePaneLayout-它没有具体联系的片段-三块是leftmiddleright。在您调用 时hideLeft(),我们会记录 和 的大小,left并将middle用于三者中任何一个的任何权重归零,因此我们可以完全控制大小。在 的时间点hideLeft(),我们将 的大小设置right为 的原始大小middle

The animations are two-fold:

动画分为两部分:

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

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

(有可能对所有这些都使用AnimatorSetand是一个更好的主意ObjectAnimators,尽管现在这可行)

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

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

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

(我的动画理解肯定还有差距,而且我喜欢括号陈述)

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

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

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

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

The activity uses a ThreePaneLayoutto hold a pair of ListFragmentwidgets 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 FragmentTransactionsfor affecting the animations, we are stuck managing that "back stack" ourselves.

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

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 NineOldAndroidsfor the animation:

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

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

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

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.

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

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

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

Thanks again to everyone who has contributed!

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

回答by weakwire

Uploaded my proposal at github(Is working with all android versions though view hardware acceleration is strongly recommended for this kind of animations. For non hardware accelerated devices a bitmap caching implementation should fit better)

github 上上传了我的建议 (尽管强烈建议对此类动画使用查看硬件加速,但可以与所有 android 版本一起使用。对于非硬件加速设备,位图缓存实现应该更适合)

Demo video with the animation is Here(Slow frame rate cause of the screen cast. Actual performance is very fast)

带有动画的演示视频在这里(屏幕投射的帧速率慢的原因。实际性能非常快)



Usage:

用法:

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


Explaination:

说明

Created a new custom RelativeLayout(ThreeLayout)and 2 custom Animations(MyScalAnimation, MyTranslateAnimation)

创建了一个新的自定义RelativeLayout(ThreeLayout)和 2 个自定义动画(MyScalAnimation, MyTranslateAnimation)

ThreeLayoutgets the weight of the left pane as param ,assuming the other visible view has weight=1.

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

So new ThreeLayout(context,3)creates a new view with 3 children and the left pane with have 1/3 of the total screen. The other view occupies the all available space.

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

It calculates width at runtime,a safer implementation is that the dimentions are be calculated first time in draw(). instead of in post()

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

Scale and Translate animations actually resize and move the view and not pseudo-[scale,move]. Notice that fillAfter(true)is not used anywhere.

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

View2 is right_of View1

View2 是 right_of View1

and

View3 is right_of View2

View3 是 View2 的 right_of

Having set these rules RelativeLayout takes care of everything else. Animations alter the margins(on move) and [width,height]on scale

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

To access each child (so that you can inflate it with your Fragment you can call

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

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Below are demonstrated the 2 animations

下面展示了2个动画



Stage1

阶段1

---IN Screen----------!-----OUT----

[View1][_____View2_____][_____View3_____]

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

[查看1][_____查看2_____][_____查看3_____]

Stage2

第二阶段

--OUT-!--------IN Screen------

[View1][View2][_____View3_____]

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

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

回答by coda

We built a library called PanesLibrary which solves this problem. It's even more flexible than what's been previously offered because:

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

  • Each pane can be dynamically sized
  • It allows for any number of panes (not just 2 or 3)
  • Fragments inside of panes are correctly retained on orientation changes.
  • 每个窗格都可以动态调整大小
  • 它允许任意数量的窗格(不仅仅是 2 或 3)
  • 窗格内的片段在方向更改时正确保留。

You can check it out here: https://github.com/Mapsaurus/Android-PanesLibrary

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

Here's a demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

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

It basically allows you to easily add any number of dynamically sized panes and attach fragments to those panes. Hope you find it useful! :)

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

回答by DanielGrech

Building off one of the examples you linked to (http://android.amberfog.com/?p=758), how about animating the layout_weightproperty? This way, you can animate the change in weight of the 3 fragments together, AND you get the bonus that they all slide nicely together:

构建您链接到的示例之一(http://android.amberfog.com/?p=758),如何为该layout_weight属性设置动画?通过这种方式,您可以将 3 个片段的重量变化一起动画化,并且您会得到它们都很好地滑动在一起的好处:

Start with a simple layout. Since we're going to be animating layout_weight, we need a LinearLayoutas the root view for the 3 panels.:

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

<?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>

Then the demo class:

然后是演示类:

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;
        }
    }
}

Also this example doesn't use FragmentTransactions to achieve the animations (rather, it animates the views the fragments are attached to), so you would need to do all the backstack/fragment transactions yourself, but compared to the effort of getting the animations working nicely, this doesnt seem like a bad trade-off :)

此外,此示例不使用 FragmentTransactions 来实现动画(而是为片段所附加的视图设置动画),因此您需要自己完成所有后台堆栈/片段事务,但与使动画工作的努力相比很好,这似乎不是一个糟糕的权衡:)

Horrible low-res video of it in action: http://youtu.be/Zm517j3bFCo

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

回答by Thierry-Dimitri Roy

This isn't using fragments.... It's a custom layout with 3 children. When you click on a message, you offset the 3 childrens using offsetLeftAndRight()and a animator.

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

In JellyBeanyou can enable "Show layout bounds" in the "Developper Options" settings. When the slide animation is complete, you can still see that the left menu is still there, but underneath the middle panel.

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

It's similar to Cyril Mottier's Fly-in app menu, but with 3 elements instead of 2.

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

Additionnally, the ViewPagerof the third children is another indication of this behavior: ViewPagerusually uses Fragments (I know they don't have to, but I have never seen an implementation other that Fragment), and since you can't uses Fragmentsinside another Fragmentthe 3 children are probably not fragments....

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

回答by boblebel

I am currently trying to do something like that, except that Fragment B scale to take the available space and that the 3 pane can be open at the same time if there is enough room. Here is my solution so far, but i'm not sure if i'm going to stick with it. I hope someone will provide an answer showing The Right Way.

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

Instead of using a LinearLayout and animating the weight, I use a RelativeLayout and animate the margins. I'm not sure it's the best way because it require a call to requestLayout() at each update. It's smooth on all my devices though.

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

So, I animate the layout, i am not using fragments transaction. I handle the back button manually to close fragment C if it is open.

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

FragmentB use layout_toLeftOf/ToRightOf to keep it aligned to fragment A and C.

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

When my app trigger an event to display fragment C, I slide-in fragment C, and i slide-out fragment A at the same time. (2 separate animation). Inversely, when Fragment A open, i close C at the same time.

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

In portrait mode or on smaller screen, i use a slightly different layout and slide Fragment C over the screen.

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

To use percentage for the width of Fragment A and C, i think you would have to compute it at run time... (?)

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

Here is the activity's layout:

这是活动的布局:

<?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>

The animation to slide FragmentC in or out:

将 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;
}