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

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:
Upon some UI event (e.g., tapping on something in Fragment B), we want:
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
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 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:
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 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.
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).
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.
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.

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)
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)
ThreeLayout gets the weight of the left pane as param ,assuming the other visible view has 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.
It calculates width at runtime,a safer implementation is that the dimentions are be calculated first time in draw(). instead of in post()
Scale and Translate animations actually resize and move the view and not pseudo-[scale,move]. Notice that fillAfter(true) is not used anywhere.
View2 is right_of View1
and
View3 is right_of View2
Having set these rules RelativeLayout takes care of everything else. Animations alter the margins (on move) and [width,height] on scale
To access each child (so that you can inflate it with your Fragment you can call
public FrameLayout getLeftLayout() {}
public FrameLayout getMiddleLayout() {}
public FrameLayout getRightLayout() {}
Below are demonstrated the 2 animations
Stage1
---IN Screen----------!-----OUT----
[View1][_____View2_____][_____View3_____]
Stage2
--OUT-!--------IN Screen------
[View1][View2][_____View3_____]

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!

We built a library called PanesLibrary which solves this problem. It's even more flexible than what's been previously offered because:
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.
You can check it out here: https://github.com/Mapsaurus/Android-PanesLibrary
Here's a demo: 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! :)

Building off one of the examples you linked to (http://android.amberfog.com/?p=758), how about animating the layout_weight property? 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:
Start with a simple layout. Since we're going to be animating layout_weight, we need a LinearLayout as the root view for the 3 panels.:
<?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 :)
Horrible low-res video of it in action: http://youtu.be/Zm517j3bFCo

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.
In JellyBean you 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.
It's similar to Cyril Mottier's Fly-in app menu, but with 3 elements instead of 2.
Additionnally, the ViewPager of the third children is another indication of this behavior: ViewPager usually 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 Fragments inside another Fragment the 3 children are probably not fragments....

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.
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.
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.
FragmentB use layout_toLeftOf/ToRightOf to keep it aligned to fragment A and 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.
In portrait mode or on smaller screen, i use a slightly different layout and slide Fragment C over the screen.
To use percentage for the width of Fragment A and C, i think you would have to compute it at run time... (?)
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:
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;
}

Related

Overlapping Fragments and Touch Events

I have a ViewPager with a custom PagerAdapter that displays a set of fragments.
These fragments are (purposely) positioned on top of each other so that I can use a PageTransformer that makes it look as if the user is sliding the fragments from a stack (almost like a deck of cards).
The issue is that each fragment has their own Views/Widgets (e.g. a seekbar) which, due to the overlapping, are occupying the same coordinates and sometimes the touch event is caught by the fragment bellow the current one (e.g. the user adjusts a seekbar's position, but instead of updating the currently shown seekbar, it's the seekbar in the next fragment that's gets its progress updated).
I've come across this answer but it's not the same exact problem.
Has anyone ever found a similar issue? What's the smartest way (except for the lazy solution: change the PageTransformer to one that doesn't overlap the fragments) of dealing with this issue?
EDIT:
In my Fragment class I have:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment, container, false);
rootView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
}
as suggested by Zsombor Erdődy-Nagy, but this doesn't help: it's still possible for the widget bellow the current fragment to receive the event instead of the current one's.
I've also looked at this open issue, with no success.
If you are still looking for the solution, than you should look at this:
Issue 58918.
Here you can find the answer to your problem. Quoting from the link:
If I remember right it was after 4.1 that the framework respects a
custom child drawing order as implied Z-ordering for dispatching touch
events. If your views overlap after this page transformation they may
not receive touch events in the expected order on older platform
versions. Check which view is receiving the touch events to be
certain.
If this is what you are seeing you have a few options:
Enforce the desired ordering as you add/remove child views in your PagerAdapter
Remove the X translation applied by the PageTransformer when a page is no longer fully visible - i.e. the "position" parameter reports a full -1 or 1.
Example:
this.viewPager.setPageTransformer(true, new PageTransformer() {
#Override
public void transformPage(View page, float position) {
float translationX;
float scale;
float alpha;
if (position >= 1 || position <= -1) {
// Fix for https://code.google.com/p/android/issues/detail?id=58918
translationX = 0;
scale = 1;
alpha = 1;
} else if (position >= 0) {
translationX = -page.getWidth() * position;
scale = -0.2f * position + 1;
alpha = Math.max(1 - position, 0);
} else {
translationX = 0.5f * page.getWidth() * position;
scale = 1.0f;
alpha = Math.max(0.1f * position + 1, 0);
}
ViewHelper.setTranslationX(page, translationX);
ViewHelper.setScaleX(page, scale);
ViewHelper.setScaleY(page, scale);
ViewHelper.setAlpha(page, alpha);
}
});
In this case all your fragments must have a background View that stops TochEvents propagating to fragments in the back.
I'm guessing that you already have opaque backgrounds for these fragments, or else the fragments in the back would show through the fragment currently on the top of the stack. So try setting a ClickListener for all your fragment's root ViewGroup instance that does nothing, it just catches the touch events.
In case anyone runs into this issue (which can happen not only with ViewPager), it is just a matter of adding this attribute to the layout at the top of your hierarchy:
android:clickable="true"
This way it will handle the clicks, not doing really anything with them, but at least avoiding them to reach the fragment in the background.

Suggestion to use layout

I have 3 views on a screen in android, assume like set of buttons, map, list and another information view. these are all present vertically.
So, if i click on list, map should get updated & also buttons color should change. Some time if i click on button information should display.
In this scenario, is it good to use fragments? or Relative layout?. suggest
Thanks
I would go with a Activity that consists of a RelativeLayout, with 3 FrameLayouts inside the RelativeLayout. I would then add Fragments in code, to the FrameLayouts. 1 fragment to each. This way you can easily move the fragments in any way you desire. Sliding menus, top sliding, side sliding, over and under, so easy. So easy if you set it up like this.
When the far left is selected you can shove ther other 2 the right, when the center receives focus slide the left one to the left and right one right, and when the far right one gets focus, slide the other to to the left.
Or you can do top to bottom.
Or you can just have all 3 have equal space, at all times.
Or you can always shove the non-focused ones to the right, left, top, bottom, the possibilities are endless. You can shrink the unfocused 2 to 1/4 size and shove them to one side of the screen, one o top and one on bottom.
See where I'm going?
otherwise do a linearlayout, with 3 framelayouts, and set each framelayout to weight = 1 (may have to toggle a few other options to keep them perfectly even at all times), then add your fragments.
public void swapfragment(int fragId, Bundle args, boolean slide)
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
switch (fragId)
{
case FRAGID_DEVICE:
currentFrag = new FragmentDevice();
currentFrag.setArguments(args);
((FragmentDevice) currentFrag).initialize();
break;
case FRAGID_NETWORK:
currentFrag = new FragmentNetwork();
currentFrag.setArguments(args);
((FragmentNetwork) currentFrag).initialize();
break;
}
ft.replace(R.id.flFragHost, currentFrag).commit();
if (slide)
slideFragment();
}
private void slideFragment()
{
final Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
if (isFragmentOut)
{
isFragmentOut = false;
Animation slideOutAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_right_out_80);
AnimationListener listener = new Animation.AnimationListener()
{
public void onAnimationStart(Animation animation)
{
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationEnd(Animation animation)
{
int pushback = (int) (displaySize.x * .8f);
rlFragHost.clearAnimation();
RelativeLayout.LayoutParams FragContainerParams;
FragContainerParams = (android.widget.RelativeLayout.LayoutParams) rlFragHost.getLayoutParams();
FragContainerParams.setMargins(pushback, 0, pushback * -1, 0);
rlFragHost.setLayoutParams(FragContainerParams);
}
};
slideOutAnimation.setAnimationListener(listener);
rlFragHost.startAnimation(slideOutAnimation);
}
else
{
isFragmentOut = true;
Animation slideInAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_left_in_80);
AnimationListener listener = new Animation.AnimationListener()
{
public void onAnimationStart(Animation animation)
{
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationEnd(Animation animation)
{
rlFragHost.clearAnimation();
RelativeLayout.LayoutParams FragContainerParams;
FragContainerParams = (android.widget.RelativeLayout.LayoutParams) rlFragHost.getLayoutParams();
FragContainerParams.setMargins(0, 0, 0, 0);
rlFragHost.setLayoutParams(FragContainerParams);
}
};
slideInAnimation.setAnimationListener(listener);
rlFragHost.startAnimation(slideInAnimation);
}
}
I know this isn't exactly what you need, but it should get you started. this is how I do sliding menus.
I think a lot of it is personal preference. My particular favorite is the linear layout. You mentioned that you want to present the items vertically, well, just set android:orientation="vertical", put your items in the xml file in order, and there you go. I've never used fragments personally, so I can't speak to their usefulness, but the linear layout has yet to let me down.

Can I partially hide a layout?

As I've a master in MS Paint, I will just upload a picture selfdescripting what I'm trying to achieve.
I've searched, but I'm not really sure what do I've to search. I've found something called Animations. I managed to rotate, fade, etc an element from a View (with this great tutorial http://www.vogella.com/articles/AndroidAnimation/article.html)
But this is a bit limited for what I'm trying to achieve, and now, I'm stuck, because I don't know how is this really called in android development. Tried words like "scrollup layouts" but I didn't get any better results.
Can you give me some tips?
Thank you.
You can see a live example, with this app: https://play.google.com/store/apps/details?id=alexcrusher.just6weeks
Sincerely,
Sergi
Use something like this as your layout (Use Linear, Relative or other layout if you wish):
<LinearLayout
android:id="#+id/lty_parent">
<LinearLayout
android:id="#+id/lyt_first" />
<LinearLayout
android:id="#+id/lyt_second"/>
</LinearLayout>
And then in an onClick method on whatever you want to use to control it, set the Visibility between Visible and Gone.
public void buttonClickListener(){
((Button) findViewById(R.id.your_button))
.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (lyt_second.getVisibility() == View.GONE) {
lyt_second.setVisibility(View.VISIBILE);
}
else {
lyt_second.setVisibility(View.GONE);
}
});
Which is fine if you just want a simple appear/disappear with nothing fancy. Things get a little bit more complicated if you want to animate it, as you need to play around with negative margins in order to make it appear to grow and shrink, like so:
We use the same onClick method that we did before, but this time when we click it starts up a custom SlideAnimation for the hidden/visible view.
#Override
public void onClick(View v) {
SlideAnimation slideAnim = new SlideAnimation(lyt_second, time);
lyt_second.startAnimation(slideAnim);
}
The implementation of the SlideAnimation is based on a general Animation class, which we extend and then Override the transformation.
public SlideAnimation(View view, int duration) {
//Set the duration of the animation to the int we passed in
setDuration(duration);
//Set the view to be animated to the view we passed in
viewToBeAnimated = view;
//Get the Margin Parameters for the view so we can edit them
viewMarginParams = (MarginLayoutParams) view.getLayoutParams();
//If the view is VISIBLE, hide it after. If it's GONE, show it before we start.
hideAfter = (view.getVisibility() == View.VISIBLE);
//First off, start the margin at the bottom margin we've already set.
//You need your layout to have a negative margin for this to work correctly.
marginStart = viewMarginParams.bottomMargin;
//Decide if we're expanding or collapsing
if (marginStart == 0){
marginEnd = 0 - view.getHeight();
}
else {
marginEnd = 0;
}
//Make sure the view is visible for our animation
view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Setting the new bottom margin to the start of the margin
// plus the inbetween bits
viewMarginParams.bottomMargin = marginStart
+ (int) ((marginEnd - marginStart) * interpolatedTime);
// Request the layout as it happens so we can see it redrawing
viewToBeAnimated.requestLayout();
// Make sure we have finished before we mess about with the rest of it
} else if (!alreadyFinished) {
viewMarginParams.bottomMargin = marginEnd;
viewToBeAnimated.requestLayout();
if (hideAfter) {
viewToBeAnimated.setVisibility(View.GONE);
}
alreadyFinished = true;
}
hideAfter = false;
}
}
EDIT: If anyone had used this code before and found that if you click on the button that starts the animation more than once before the animation was finished, it would mess up the animation from then on, causing it to always hide the view after the animation finished. I missed the reset of the hideAfter boolean near the bottom of the code, added it now.
you can do this manually by using setvisibility feature on the event onClick()
or
use this
dynamically adding two views one below other

Android: call requestLayout() after update v.layout parameter causes layout reverts back to it's original position

I have framelayout which contains two relative layouts, one is on top of the other. When user clicks a button, the one on the top move 80% off the screen to the right. Then one on the bottom becomes clickable. This is what it looks like.
FrameLayout
RelativeLayout (bottom) RelativeLayout (top)
FilterWidgets Open/close button, ListView
It's really easy to achieve on 3.0+ with the new animation api which is Property base Animation. For the pre 3.0, because animation is view based. So I end up manually modify the layout property on onAnimationEnd. The call requestLayout to make it permanent, but only to find out the layout reverts back to original position. Anybody know how to move layout permanently?
see my other post if you want to see the whole picture:
https://stackoverflow.com/questions/14541265/changecursor-cause-layout-container-of-the-listview-to-reposition
theTranslationX.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator nullPointer) {
v.clearAnimation();
int theL = isMenuOn() ? 0 : v.getLeft() + getFilterWidth();
int theR = isMenuOn() ? v.getWidth() : v.getLeft() + getFilterWidth() + v.getWidth();
int theHeight = v.getHeight();
int theT = 0;
v.layout(theL, theT, theR, theHeight);
v.requestLayout();
}
});
This is 9 months late but try using:
yourView.layout(left,top,right,bottom); //all parameters are type int
However I don't think this is permanent, the position of the view will still be reset when you call requestLayout(), but give it a try.

Animate a Fragment so that it is partially offscreen

I would like to partially hide a fragment when a new fragment appears.
I am building on ScienceGuy's code on GitHub, as referenced on Fragment Animation like Gmail Honeycomb App
I start with three fragments. The left most has a weight of 6, the middle one a weight of 4, and the right most a weight of 4. Setting the weightSum to 10 makes the left and middle fragments appear, and the right most is offscreen. This is what I want.
When the user selects an item in the middle fragment, I would then like to transition the left fragment so that two thirds of it moves off the left side of the screen. Because of the weights, I would effectively have 2, 4 , 4. At this point, the right fragment has moved onto the screen completely.
Using ScienceGuy's code, I can get the right fragment to appear when the left fragment scrolls off, but the left fragment completely disappears.
I have tried using the following code:
final float middleFragmentWidth = getMiddleFragment().getView().getWidth();
ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "x",
-middleFragmentWidth/2, 0).setDuration(
trans.getDuration(LayoutTransition.APPEARING));
trans.setAnimator(LayoutTransition.APPEARING, animIn);
ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "x", -middleFragmentWidth,
-middleFragmentWidth/2).setDuration(
trans.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
trans.setAnimator(LayoutTransition.DISAPPEARING, animOut);
I suspect that Android expects that a fragment is completely hidden when one "hides" it. Is there a way to animate fragments so that they are not actually hidden, but partially offscreen?
Alternatively, I could put the three fragments into a HorizontalScrollView, and try animating that. However, I've not done that because the middle and right most fragments contain listviews, and using a listview inside a HorizontalScrollView is to be avoided according to the docs.
I ended up creating a custom view based on LinearLayout which is 150% of the width of the device.
public class AutoSizingLinearLayout extends LinearLayout {
private static final String TAG = AutoSizingLinearLayout.class.getSimpleName();
final int myWidth;
public AutoSizingLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
myWidth = setScaledWidth(context);
}
public AutoSizingLinearLayout(Context context) {
super(context);
myWidth = setScaledWidth(context);
}
private int setScaledWidth(Context context) {
if (this.isInEditMode()){
return 1000;
} else {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return (int) (1.5 * wm.getDefaultDisplay().getWidth());
}
}
#Override
public android.view.ViewGroup.LayoutParams getLayoutParams() {
android.view.ViewGroup.LayoutParams params = super.getLayoutParams();
params.width = myWidth;
return params;
}
And then I animate it like this.
ObjectAnimator a = ObjectAnimator.ofFloat(linearView,"translationX", -scrollamount).setDuration(900);
a.start();

Categories

Resources