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.
Related
I'm struggled with this for hours looking for solution on google and stackoverflow. Thought that this is some trivial bug in my app but finally made empty project and can reproduce this too. Just run new project and select "Tabbed Activity" with navigation style "Action Bar Tabs (with ViewPager)
Then try to put any widget at the bottom of the fragment's layout. I did this by modify fragment_main.xml and adding:
android:layout_height="match_parent"
android:gravity="bottom"
android:textAlignment="center"
So the whole layout is:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity$PlaceholderFragment">
<TextView
android:id="#+id/section_label"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:gravity="bottom"
android:text="aaaaaaa"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="#+id/constraintLayout"
tools:layout_constraintLeft_creator="1"
tools:layout_constraintTop_creator="1" />
</android.support.constraint.ConstraintLayout>
In design mode everything looks fine:
But when you run app:
You will see text only when you swipe action bar to hide it:
So it is impossible to set widget at the bottom of the tab's fragment or even match some list/picture to the height of the parent because bottom edge will be always under navigation bar.
Workaround which I found is here:
ViewPager with Toolbar and TabLayout has wrong height
First one is to put AppBarLayout and ViewPager between LinearLayout but then I lose hidding action bar functionality when scrolling ViewPager's content. Second one is add android:paddingBottom="?attr/actionBarSize" in ViewPager but then there is a gap when I hide action bar. Seriously there is no solution for this?
I think this is an expected behavior since the ActionBar gets hidden when scrolling up. In the design mode the text can be shown because it doesn't display the TabLayout. However, when you launch the app, it will inflate the TabLayout and the fragment will go below it. So it's not like the fragment is getting expanded or giving you wrong height.
Imagine putting an ImageView that has a matching height of the visible field (from below the TabLayout to right above the navigation menu). When you hide action bar from there, it will have a gap on the bottom since there's no content to fill up the space of hidden action bar, unless you stretch the ImageView as you scroll up, which will result in wired stretched image :/
One possible solution I can think of is, if you want to add a view on the bottom of the fragment, I will set the actionbar padding to the view and when I scroll the screen, I will adjust the padding depends on the scroll offset so that I can always be on the bottom.
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.
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 am adding a fragment in portrait, rotating to landscape, then rotating back to portrait. After the second rotation, the home up button turns black. Any idea what might be causing this?
I am using theme Theme.AppCompat.Light.DarkActionBar.
I am using ActionBar on this fragment, but Toolbar on a fragment that is added in landscape.
If I add the fragment in landscape first, then switch to portrait, it is black.
If I rotate a third time to landscape it turns white, then black again when back to portrait.
I am using this same Fragment in another Activity and I don't see this behavior.
I had the exact same problem as you. I use the toolbar in an activity containing a navigation drawer that hosts a number of frgaments some of which have listviews. Selecting an item in the listview starts a new details activity where I am using the actionbar and not the toolbar. On rotation the home up arrow changes to a dark grey and on subsequent rotations stays that color and does not change back to white even though I am using the Theme.AppCompat.Light.DarkActionBar for the details activity.
I resolved this issue by adding a toolbar to my details layout and applying a dark actionbar theme directly to the tool bar
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/details_toolbar"
android:layout_width="match_parent"
android:layout_height="#dimen/abc_action_bar_default_height_material"
android:background="#drawable/actionbar_background"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
I do not reference the above toolbar in my activity or fragment and still use the actionbar as I did before. I do not set the toolbar as the actionbar either using setSupportActionBar(mToolbar); it's just present in my layout and nothing more than that, but now when I rotate the device the home up arrow does not change color and stays white as expected. It's strange, but works.
Edit:
I should have mentioned that the placement of toolbar in my details activity is such that it is not actually visible. Specifying the visibility of the toolbar to invisible still works android:visibility="invisible"
Edit: I wondered why this worked and it seemed very strange to me so I completely removed the toolbar and added the theme to the view group at the root of my layout. That makes more sense now.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:id="#+id/details_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
The latest Android Support Library introduced the DrawerLayout to implement the common UX pattern where you slide right or left to show a navigation menu.
What I'd love to have is a vertical DrawerLayout with the same API, that can be pulled down/up from the top/bottom of my layout.
Since 4.2 the old SlidingDrawer has been deprecated and I haven't heard about some new Widget that implements the same functionality.
Can the DrawerLayout be extended somehow to implement the vertical swipe UX pattern?
Does google provide some different widget to implement it?
Google Music for instance has something very similar to what I'm looking to implement to pull up the player.
We have recently implemented this in the Umano App and open sourced: https://github.com/umano/AndroidSlidingUpPanel
Enjoy.
The Android support library now has the bottom sheets behavior to do that.
Check out this link for more info https://material.google.com/components/bottom-sheets.html
Nowadays, it makes more sense to use the BottomSheetBehavior that you can find more information on how setting it up on https://code.tutsplus.com/articles/how-to-use-bottom-sheets-with-the-design-support-library--cms-26031
Basically, you need to set your main content, and your sliding content. The BottomSheetBehavior would only work for panels that you slide from the bottom to the top.
It has a quite simple set up and the BottomSheetBehavior could even work out of the box. Only by writing a android.support.design.widget.CoordinatorLayout layout, with another View inside (with even wrap_content as a value in the layout_height parameter), for instance a LinearLayout like this one:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="56dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<!-- Your content goes here -->
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
In my case, I inflate this layout in a Fragment and add it to the Activity where you want to enable the SlidingSheetBehavior.