The proposed practise for the new navigation components were presented at I/O with the following template and proposed philosophy:
One Activity for an App
Activity contains Toolbar and Bottom Navigation Bar
A typical app often has a detail view with a CollapsingToolbar in it. How would one build that under that architecture?
Move Toolbar to each Fragment XML?
Implement the collapsing toolbar programmatically?
Move the detail fragment to its own activity (it may use its own deeplink anyway) and 'break' the philosophy?
A typical app often has a detail view with a CollapsingToolbar in it. How would one build that under that architecture?
Great question! I struggled with this for a bit as well and came to the conclusion that there should be one Activity with a NavHostFragment and, ideally, nothing else. This gives you ultimate flexibility to display (or not display) whatever you need for each screen. Importantly, make sure your theme removes the ActionBar:
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
Which leads to your next question...
Move Toolbar to each Fragment XML?
In my opinion, yup! Everything you'd typically use the ActionBar for can be done via a Toolbar. Here's a quick snippet that shows how a Toolbar can be used to do the most important things you've used ActionBar for in the past (up navigation, title, options menu, etc...):
toolbar.apply {
setNavigationOnClickListener { findNavController().navigateUp() }
setTitle(R.string.toolbar_title)
inflateMenu(R.menu.fragment_menu)
setOnMenuItemClickListener(::onMenuItemClick)
}
Implement the collapsing toolbar programmatically?
It depends on what exactly you are trying to do, but most likely, there's no need for that. You can drop an AppBarLayout, CollapsingToolbarLayout, and Toolbar into your layout and use them just like normal. Give your AppBarLayout an ActionBar theme overlay. Here's an example:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="#color/primary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="#drawable/ic_up_white"/>
...
Move the detail fragment to its own activity (it may use its own deeplink anyway) and 'break' the philosophy?
No need for that with the above, right? It's an approach that's flexible enough to accommodate multiple levels easily in one nav graph and still be able to customize the appearance and behavior of every destination in the graph (including ActionBar-like functionality).
try
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
if(expandToolbar){
appBarLayout.setExpanded(true,true);
}else{
appBarLayout.setExpanded(false,true);
}
Here is a usufal link
disable expand on CollapsingToolbarLayout for certain fragments
also for other people inteasing of changing some parts of their toolBar
you should write your custom toolbar view in separate XML and try to inflate the custom view in your details Fragment grammatically then hide the unused elements
of the old toolbar if there are any.
setSupportActionBar(toolbar);
View logo = getLayoutInflater().inflate(R.layout.view_logo, null);
toolbar.addView(logo);
and here is how u can hide unwanted Views
for (int i = 0; i < toolbar.getChildCount(); ++i) {
View child = toolbar.getChildAt(i);
// here u can hide all text views for example.
if (child instanceof TextView) {
child.setVisibility(View.GONE );
}
}
this way is a lot better than writing two activities
Let's assume that we have
One Activity for an App
Activity contains Toolbar and Bottom Navigation Bar
All possible appearances for the toolbar that you need for your app should be implemented in that single toolbar and controllable be the currently active fragment.
Not to violate the Dependency inversion principle all Fragments that need a feature from the activity's toolbar must implement an interface. You could use the OnBackStackChangedListener to check for updates of the view
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment visibleFragment = ...
if(visibleFragment instanceof ToolbarControlFragment) {
if(visibleFragment.shouldExpandToolbar()) {
// set AppBarLayout expanded state
}
// ...
}
}
}
);
You maybe remember that principle when a Fragment requires an OptionsMenu.
I would generally recommend having only one Bottom Navigation Bar controlled by an activity and several Toolbars in Fragments. This reduces complexity and makes components of the app more independent.
Related
I am trying to integrate Collapsing layout with Navigation Advanced example
What I tried?
Added Collapsing bar layout to main_actvity.xml
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
Modified setupBottomNavigationBar() in MainActivity.kt
private fun setupBottomNavigationBar(){
...
controller.observe(this, Observer { navController ->
mainBinding.collapsingToolbarLayout.setupWithNavController(mainBinding.toolbar, navController)
//setupActionBarWithNavController(navController)
})
...
}
Current issue:
Now I have two ActionBars instead of one. Top one have app name as the title, one below shows nothing initially but when navigated to an sub destination only a grey back arrow is shown, no destination label is shown on the actionbar
Anyone who understand this multiple backstack implementation, please help me to get things work with CollapsingToolbarLayout
Update:
Got rid of one action bar as #Manoj suggested in a comment, Now need to fix not appearing of titles in the actionbar
Update 2:
Although the destination labels(titles) are not shown when navigated to sub destinations, but back button is shown.
if the title is not displayed (and that increasing the appbar layout height "works"), just set the isTitleEnabled of the collapsingToolbarLayout to false.
It should fix the problem
I finally figured out why the toolbar title was not shown reason was I have not set enough height for appbar layout, I was using wrap_content so collapsing toolbar layout was covering the toolbar title. Solution was to set appbar layout height to value larger than 64dp. When I increased the appbar layout height, it looks unusually tall. (Forgive me for my lack of understanding of how collapsing toolbar works)
But this was not my intention, I wanted to enable collapsing toolbar for some specific fragments, I was using single activity concepts as navigation architecture component recommends.
As I read in following answers
Having two toolbars and making one transparent when doing fragment transactions.
Having separate toolbar for each fragment
IMHO Both of these are not good solutions if you are using navigation architecture component, there is no value of using navigation architecture component, if you need to manage fragment transactions or toolbars manually.
So for now I have stop using collapsing toolbar.
I have just started migration to material theme using support library 23.1.
I have looked up the guides and all revolve around the following procedure..
1.Use theme without ActionBar provided by decor. I.e.
<style name="MyTheme" parent="Theme.AppCompat">
...
</style>
2.Put a Toolbar widget somewhere in activity layout xml:
<android.support.v7.widget.Toolbar
android:id=”#+id/myToolbar”
android:layout_height=”wrap_content”
android:layout_width=”match_parent”
android:minHeight=”?attr/actionBarSize”
android:background=”?attr/colorPrimary” />
3.In code set this toolbar as an action bar:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
Toolbar toolbar = (Toolbar) findViewById(R.id.my_awesome_toolbar);
setSupportActionBar(toolbar);
}
The problem is that some of my activities use FrameLayout as a root layout and as a consequence a toolbar is placed on top of the screen and overlays existing content and therefore hides it.
One workaround would be to create vertically oriented LinearLayout and put Toolbar and FrameLayout as it's children. But, this ads complexity, another level of Views and affects performance.
My first question would be how does mechanism of setSupportActionBar(toolbar) works? Does it takes the toolbar out of its place as set in xml layout and reinserts it at the top of containing layout. Or it just leaves it there where it is?
And the main point, is it possible to use the new Toolbar as decor provided ActionBar? Leave my code unchanged and it will automagically use Toolbar instead of ActionBar? Is it possible to achieve this?
EDIT - UPDATE:
If I leave my code unchanged as if I want to leave the old ActionBar the menu in not instantiated.
EDIT - UPDATE
As far as I could gather there is no way to persuade android to create Toolbar instead of action bar. This sadly means refactoring of all activities and adding another outer enclosing Layout thereby needlessly increasing complexity.
First question: How does the setSupportActionBar(toolbar) works?
What the above statement does is make the toolbar act like an ActionBar. By default the toolbar does not have action bar capabilities, and to make the toolbar act like an action bar, you need this statement. The followin is the official documentation of this method.
Set a Toolbar to act as the ActionBar for this Activity window.
When set to a non-null value the getActionBar() method will return an
ActionBar object that can be used to control the given toolbar as if
it were a traditional window decor action bar. The toolbar's menu will
be populated with the Activity's options menu and the navigation
button will be wired through the standard home menu select action.
setSupportActionBar replaces the decor action bar with the toolbar that you are supplying as a view in the activity. This means that unlike action bar, toolbar will live as a view in your activity. It doesn't take the toolbar out of the layout and reinserts it at the top of containing layout. This means that you can have the toolbar anywhere you want in the screen, not necessarily at the top.
Second Question: Leave my code unchanged and it will automagically use Toolbar instead of ActionBar?
No, you can't do this. Toolbar has to be present in your layout, (either via xml or inflated via code). This is the whole purpose of the toolbar. With action bar you don't have this control. Toolbar allows you to modify it as much as possible and you can a different one in different activity. So how do you do it? What's the best practice?
Well, it's recommended that all activities have an accompanying fragment. So you could have a common layout for all activities (like the one below). Now, you could just inflate the fragment in the FrameLayout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
of course you don't have to add the toolBar if you don't want to (but you will lost some of the cool navigation patterns the toolbar is involved at).
Just make your activity extend a theme with toolbar and you will find it there where its supposed to be.
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
Then in your activity you will be able to get ActionBar instance as usual:
ActionBar actionBar = getSupportActionBar();
So, I have an activity with navigation view. By click on its item I change fragment in activity. All fragment have the same toolbar. But one have this toolbar and TabLayout to it. I would like to know what is better to declare toolbar once on activity like this
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/toolbar" />
</RelativeLayout>
or declare it in each fragment.
The disadvantage of the first method is default toolbar shadow. When I add tabs in fragment, shadow looks like
When I tried 2 solution. All my toolbar was with back icon instead drawer animated logo.
Thanks.
I had the exact same problem. This is how I solved it:
Move the toolbars to the fragments like you suggested (so you won't have a shadow separating the two). This allows for a way more flexible way to implement (different) toolbars in your layouts too.
Replace the Toolbar's navigation icon by a custom one like this:
toolbar.setNavigationIcon(R.drawable.ic_action_menu);
(I used the Android Asset Studio to easily create an icon with the preferred color)
Now open the NavigationView with the new menu(home) icon. You can do this through the MainActivity (the one with the NavigationView). Create a public method in that Activity that opens the drawer:
public void openDrawer(){
mDrawerLayout.openDrawer(Gravity.LEFT);
}
Now call this method in the OnOptionsItemSelected in your fragments like this:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle item selection
switch (item.getItemId()) {
case android.R.id.home: //Menu icon
((MainActivity)getActivity()).openDrawer();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
That's it. Of course the downside is that you must implement the Toolbar in each Fragment. However, this is the only way (that I know of) that enables you to have the Toolbar (+TabLayout) in a Fragment and still be able to control your NavigationView.
You can use AppBarLayout from design support library like:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
...
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
...
/>
</android.support.design.widget.AppBarLayout>
and then you can change visibility of tablayout.
For more information about desing layout library : link
I have been adding a navigation drawer to one of my apps, and I started to wonder whether or not it would be better to switch from using a ListView to multiple TextViews for the navigation drawer list items. Looking at the Google Design Guidelines on Navigation Drawer content (specifically the section on 'Scrolling'), I noticed that it may look nicer with multiple TextViews.
At the moment, I am using a ListView and ImageView in my navigation drawer (it looks a little like this. However, when I scroll in my navigation drawer (I do this by turning my device landscape as there are not enough items in my list yet), only the ListView scrolls, and the ImageView stays as it is. I want it to be able to scoll more like this, where the ImageView is also scrolled with the ListView.
Additionally, I found that my ListView in my navigation drawer does not have the ripple effects as shown in this image although other ListViews in my other Activitys and Fragments do.
What are the issues I am facing and how could I go about resolving these?
Update:
In Google's I/O App (2014), there seems to be a LinearLayout at the bottom of the navigation drawer layout which I think is responsible for the list of items shown. Could someone explain how this would work?
only the ListView scrolls, and the ImageView stays as it is
It sounds like your drawer contains an ImageView at the top and then a ListView follows. With this configuration only the ListView will scroll (because it's the only view that's scrollable).
You need to add the ImageView as a header which is always at the beginning of the list. As one of the comments suggested, do listView.addHeaderView.
there seems to be a LinearLayout at the bottom of the navigation
drawer layout which I think is responsible for the list of items
shown. Could someone explain how this would work?
They use the LinearLayout as a container to hold all the TextViews:
private void createNavDrawerItems() {
mDrawerItemsListContainer = (ViewGroup) findViewById(R.id.navdrawer_items_list);
...
int i = 0;
for (int itemId : mNavDrawerItems) {
mNavDrawerItemViews[i] = makeNavDrawerItem(itemId, mDrawerItemsListContainer);
mDrawerItemsListContainer.addView(mNavDrawerItemViews[i]);
++i;
}
}
I believe the reason they use a LinearLayout and inflate all the items programmatically is to be able to use separator items easily:
private View makeNavDrawerItem(final int itemId, ViewGroup container) {
...
if (itemId == NAVDRAWER_ITEM_SEPARATOR) {
layoutToInflate = R.layout.navdrawer_separator;
} else if (itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL) {
layoutToInflate = R.layout.navdrawer_separator;
} else {
layoutToInflate = R.layout.navdrawer_item;
}
...
return view;
}
In a ListView you'd have to create a separate item type and use the divider's layout there, which could possibly get more cumbersome.
At first glance, however, this code just seems to be re-inventing the wheel as all of this is possible with a ListView.
As of 29th May 2015 (after Google I/O 2015), you can use the Android Design Support Library to add a NavigationView to your app(s). The Android Developer Blogspot article states the following:
Navigation View
The navigation drawer can be an important focal point for identity and navigation within your app and consistency in the design here can make a considerable difference in how easy your app is to navigate, particularly for first time users. NavigationView makes this easier by providing the framework you need for the navigation drawer as well as the ability to inflate your navigation items through a menu resource.
...
You can then start using the Design library with a single new dependency:
compile 'com.android.support:design:22.2.0'
...
The Design library, AppCompat, and all of the Android Support Library are important tools in providing the building blocks needed to build a modern, great looking Android app without building everything from scratch.
Implementing scrollable Navigation Drawer using android.support.v4.widget.DrawerLayout and NavigationView could be even simpler than it is described at: http://android-developers.blogspot.ru/2015/05/android-design-support-library.html
That article suggests adding each element of your application's Navigation Drawer as a Menu Item. This is cool and definitely a way to go for most of developers.
But what if you already has a Navigation Drawer implemented inside e.g. Linear Layout?
It appears that you can easily make your old good layout scrollable: just set it as a "app:headerLayout" of the NavigationView. No more changes are needed!
So, in a final solution you will have:
A layout of your Activity, similar to the above blog post, but without an "app:menu="#menu/drawer" attribute e.g. this:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- your content layout -->
<android.support.design.widget.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/drawer_header"
/>
</android.support.v4.widget.DrawerLayout>
And a layout for all your old Drawer content in the "drawer_header.xml" file, migrated without any changes to this scrollable Drawer, E.g. this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:orientation="vertical">
<TextView
android:id="#+id/myFirstButton"
android:onClick="onMyFirstButtonClick"
android:text="#string/my_first_button_title"/>
<TextView
android:id="#+id/goToTheTopButton"
android:onClick="onGoToTheTopButtonClick"
android:text="#string/go_to_the_top_title"/>
<View style="#style/Divider"/>
<!-- Some other "menu items" -->
</LinearLayout>
For full working example see this activity layout: https://github.com/andstatus/andstatus/blob/master/app/src/main/res/layout/timeline.xml and this commit, where I migrated to a scrollable Navigation Drawer: https://github.com/andstatus/andstatus/commit/a80b299de714bdd65cacb138ffb31adc3ea23a98
I want to implement Toolbar something like the following design. It is called flexible toolbar and card toolbar
I tried implementing the custom view inside toolbar. I achieved something like below image!
Now what I did is added custom view to Toolbar but I want to implement like first image. I want to create a card like view on tool bar with all those action views.
To achieve that (As per my view) I can create a custom layout and add it over tool bar. Then I should attach another layout below the toolbar.
My question is whether I should do it as what I said or is there any other simpler way I can implement it?
You should use 2 ToolBars - One for the "ActionBar" and one for the CardView
<FrameLayout>
<ToolBar /> //the Actionbar ToolBar
<CardView layout_marginTop="?attr/actionBarSize">
<ToolBar /> //the Toolbar with the Menu items
...
</CardView>
</FrameLayout>