Complete Working Sample of the Gmail Three-Fragment Animation Scenario

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

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/tree/master/Animation/ThreePane

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


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

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.

The animations are two-fold:

  • 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

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

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

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() simply reverses the process, with the same mix of animators, just with the directions reversed.

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.

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

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!

3 pane layout fragment animation is stuttering

The problem is not ObjectAnimator, but rather that your application is simply doing too much on every frame of the animation. Specifically, you are animating layout params and requesting layout on every frame. Layout is powerful and useful... but can be quite expensive in any but the most trivial of view hierarchies. It's important to avoid expensive operations per-frame during animations, and layout falls into that "expensive" category. Sliding things around is fine (translationX/Y), fading things in/out is good (alpha), but actually laying things out on every frame? Just say no.

How can I implement fragment animation?

The problem was that I replaced the fragment first and then I applied
the animation. It has an order:

getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in,R.anim.slide_out)
.replace(R.id.frag_container, fragment)
.comit()

Google+ Style View/Fragment Animation?

This is the best resource about the side menu, there is no code but a clear explanation on how to implement it:

Fly-in app menu

and this is a post on how to animate the action bar related to the side menu:

Slide action bar

Hope it will help.



Related Topics



Leave a reply



Submit