Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write an Android multi-pane app with very deep navigation

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" thereHere, 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 layoutThe user selects Item #2, and the item detail page opens on the right paneOn 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 paneA 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.

like image 851
Kevin Read Avatar asked Dec 27 '12 11:12

Kevin Read


People also ask

What is NavHostFragment?

NavHostFragment provides an area within your layout for self-contained navigation to occur. NavHostFragment is intended to be used as the content area within a layout resource defining your app's chrome around it, e.g.: <androidx.drawerlayout.widget.DrawerLayout.

Do Android apps need a back button?

All Android devices provide a Back button for this type of navigation, so you should not add a Back button to your app's UI. Depending on the user's Android device, this button might be a physical button or a software button.

How does the Android back button work?

When you press the Back button, the current destination is popped off the top of the back stack, and you then navigate to the previous destination. The Up button appears in the app bar at the top of the screen. Within your app's task, the Up and Back buttons behave identically.

What provides easy start and navigation between application?

Android Jetpack's Navigation component helps you implement navigation, from simple button clicks to more complex patterns, such as app bars and the navigation drawer. The Navigation component also ensures a consistent and predictable user experience by adhering to an established set of principles.


1 Answers

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!

like image 61
Budius Avatar answered Oct 14 '22 03:10

Budius