11

TL;DR: How should multi-pane apps with deep navigation similar to the Spotify iPad app look and work on Android, and how to implement this?

Long version: I'm working on an app, where the user sees lists of items and can then delve deeper into these items. These item detail pages can again open lists of related items, that in turn have detail pages and so on. As a phone app, these would be separate Activities that might look and link to each other like this: User clicks on Item#2 in Overview and gets the detail page, then clicks on "List of things" there Here, List of Things is open, and the user opts to see "Thing #3"

In the mock-ups, the user sees an initial overview and then selects "Item #2" from the first list. A new Activity opens up, showing him details for Item #2. Here, he selects to see a list of Things relating to Item #2. The newly openend Activity in the third picture shows this list, and clicking on one opens the details for this thing. He can navigate as deep into the content as he likes.

This works quite well with the usual Android Activities. I'm working on bringing the app to tablets and am thinking on how to best implement this. The plan is to create a multi-pane layout with the same concept. It is very similar to how the iPad Spotify app works (it will be interesting to see how they bring this to Android once they create tablet-specific layouts).

In the tablet layout, each click on an item or list name opens the corresponding child item as a new pane that animates in from the right. The same workflow as in the example above would look like this:

Initial multi-pane tablet layout The user selects Item #2, and the item detail page opens on the right pane On the detail page, the user selects "List of things", and the right pane moves to the left, and the list of things opens in the right pane A click on Thing #3 opens the thing details in the right pane

I'm unsure how to best implement this navigation pattern. Multi-pane apps with a limited navigational depth like GMail can be built with a static ViewGroup (LinearLayout would be ok) containing all fragments, and going deeper into the navigation replaces the content of the next container to the right and animates to this (see CommonWares implementation of this on SO).

This suggests that a custom ViewGroup would be the way to go. If it has to display a subpage (i.e. "List of Things"), then it creates a new child in the ViewGroup that is half as wide the screen with the fragment and then scrolls the visible area so that the pane that was just interacted with and the new child are visible. To link this correctly to a FragmentTransaction, so that the back stack works correctly, I'd guess it would be something like this:

View newPane = container.addChild();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(newPane, new ListOfThingsFragment(2));
ft.remove(paneOnRight, fragmentOnRight);
ft.commit();
container.animateToRight();

I don't see a way to do the animation within the FragmentTransaction.

Feedback welcome. My employer is generally favorable with respect to open sourcing frameworks we develop, so if this is something that is of broader interest and if I can come up with a reusable solution, I'd be glad to share it.

Community
  • 1
  • 1
Kevin Read
  • 2,173
  • 16
  • 23
  • 2
    very interesting question, I'll be looking to possible answers here. A quick observation thou: if you keep everything in just one activity on both phone and tablets it will save you some trouble on ActA must implement this, then B implement that. It's just one, that implements everything and do all the fragment transactions. – Budius Dec 27 '12 at 11:24

3 Answers3

6

I had some research time and came up with a solution to this question (a question that I've wanted to see the solution for LONG time, even before you asked it). I can't really show the whole code as there's some IP boundaries, but I'll put it here the main parts for this animation to works.

There're two key tools: setCustomAnimations and LayoutTransition Yes, as far as I've been able to do it, you need to separate set animations to make it work.

So let's get to some code, you'll define your XML with a horizontal LinearLayout and make sure to include the following line on it.

android:animateLayoutChanges="true"

this will auto-generate a standard LayoutTransition which does translate the fragment/view that is staying in the layout and alpha (in or out) the fragment/view that is being included or removed from the layout. Give it a try.

So after this layout is inflated we gonna capture this LayoutTransition and trick it out to our needs:

LayoutTransition lt = myLinearLayout.getLayoutTransition();
lt.setAnimator(LayoutTransition.APPEARING, null);
lt.setAnimator(LayoutTransition.DISAPPEARING, null);
lt.setStartDelay(LayoutTransition.CHANGE_APPEARING, 0);
lt.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);

with that code, we're removing the alpha animations and removing any delay from the transition (because we want all the translations to fire together).

And now it's just a few simple fragment transactions to make it work, during initialisation we inflate that layout and put a few fragments on it:

setContentView(R.layout.main); // the layout with that Linear Layout
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.main, frag1, FRAG_1_TAG); // it's good to have tags so you can find them later
ft.add(R.id.main, frag2, FRAG_2_TAG);
ft.add(R.id.main, frag3, FRAG_3_TAG);
ft.hide(frag3);
ft.commit();

now on the transaction it's a simple:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.push_left_in, R.anim.push_left_out, R.anim.push_right_in, R.anim.push_right_out);
Fragment left = getFragmentManager().findFragmentByTag(FRAG_1_TAG);
Fragment right = getFragmentManager().findFragmentByTag(FRAG_3_TAG);

ft.hide(left);
ft.show(right);

ft.addToBackStack(null);
ft.commit();

final notes:

to make deeper navigation it's just a matter of firing FragmentTransactions to add fragments to the LinearLayout and hide or detach the left side fragment.

Also to make the fragments work on the linear layout is important to set their LinearLayout.LayoutParams.weight during runtime, something similar to the following code applied to the fragment view

((LinearLayout.LayoutParams) view.getLayoutParams()).weight = 1;

to make it work on phones as well it's just a matter of applying the common multiple screen support patterns.

last note, be careful on proper managing the layout status during device rotation because it's not all automagically handled by the system.

Happy coding!

Budius
  • 39,391
  • 16
  • 102
  • 144
  • Thanks for that solution, it is very interesting. I wrote a custom ViewGroup to do it and am quite happy with the solution. I wasn't able to animate the movement of the pane that is staying within the fragment transaction. I like that you use standard android APIs and I'll definitely try it out. – Kevin Read Jan 04 '13 at 13:52
  • I tried this out today and it works really well for the most part. It is far more elegant than my solution with a custom ViewGroup. One issue I ran into is using the back button, when a FragmentTransaction is reversed. The LayoutTransition won't play correctly, and the fragments are moving strangely about then. Have you experienced similar issues? – Kevin Read Jan 07 '13 at 15:02
  • I found the issue. I was detaching the right-hand fragment when moving in a new one to the right. The framework optimized this by somehow replacing one of the children of the LinearLayout with the new fragment. When reversing the process, the LL did not play the remove animation. – Kevin Read Jan 07 '13 at 15:12
  • hi. yes, I'm still on it and you can solve it by using `hide()` instead of detach. This way the view still is to the left of the next fragment within the Layout and everything animates properly. I reckon `show/hide` is only calling `setVisibility()` on the views. Now I have this controller 95% complete, but depending on rotation, back button, rotation, add more fragments I still get this weird animation not working very well. – Budius Jan 07 '13 at 15:38
4

We ran into the same problem with our app. The constraints we gave ourselves:

  • Dynamic numbers of panes
  • Each pane can be differently sized
  • Fragments inside of panes must be correctly retained on orientation changes.

In light of those constraints, we built a new layout we call PanesLayout. You can check it out here:

https://github.com/cricklet/Android-PanesLibrary

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

TrevJonez
  • 949
  • 8
  • 12
coda
  • 504
  • 5
  • 13
  • Hi Coda, I have tried your code, Working Perfectly, Can you please Help me for my struck in Design. How to Add new fragment inside the fragment based on the button click action, Because i'm using the Tabbar, That contains fragment to show my views, I have plan to implement action like group chat, when the user in user profile page have a chat icon, when the user have to click chat icon, current view fragment will hide and chat fragment will Enable this is my flow, Hope u will response my queries, Thank in Advance. – MohanRaj S May 23 '16 at 06:50
2

Partial answer to the animation part: You can do animations with the FragmentTransaction:

ft.setCustomAnimations(android.R.anim.slide_in_left, 
    android.R.anim.slide_out_right);

Update: see this answer from Reto Meier himself about fragment animation: https://stackoverflow.com/a/4819665/1007169

Community
  • 1
  • 1
Mattias Isegran Bergander
  • 11,811
  • 2
  • 41
  • 49