I have the following layout: a drawer, with the main content view having a AppBarLayout, RecyclerView and a TextView. When I scroll the recycler, the toolbar is correctly hidden.
However, I have a use case: when all items from the recycler are removed, I sets its visibility to 'gone' and a TextView with an appropriate message it shown instead. If this is done while the toolbar is hidden, it is not possible for the user to see the toolbar again.
Is it possible to programmatically cause the toolbar to be fully shown? I would do this whenever the TextView is shown instead of the RecyclerView.
Here is my layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
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:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/test_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<TextView
android:id="#+id/empty_test_list_info_label"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="#dimen/spacing_big"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="gone"/>
</android.support.design.widget.CoordinatorLayout>
<RelativeLayout
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?android:attr/windowBackground"
android:elevation="10dp"/>
</android.support.v4.widget.DrawerLayout>
BTW, for some reason, if I put the AppBarLayour after the RecyclerView, as all tutorials seem to show it, it hides the topmost item from the list. It only works correctly when it is above it.
You can do it by accessing to the AppBarLayout that contains your Toolbar
Here is an example:
if (mToolbar.getParent() instanceof AppBarLayout){
((AppBarLayout)mToolbar.getParent()).setExpanded(true,true);
}
setExpanded(expand,animation) will do the work.
You can also have a a reference to the AppBarLayout instead of call the getParent from the toolbar.
You need to put header to the RecyclerView to the height of the AppBarLayout. I.e at position 0 of the RecyclerView you need to add the header and then the rest of the elements.
If you want to forcefully show the Toolbar, actually the AppBarLayout with offsetting top and bottom of the AppBarLayout dependent views (it is called Behavoir) . You need to keep reference of height of the AppBarLayout, as we know that height is the distance between top and bottom of view Rect.
Assuming that your AppBarLayout hold only a Toolbar:
int mAppBarLayoutHeight = mAppBarLayout.getBottom() - mAppBarLayout.getTop(); //initial, normal height
private void showToolbar(){
if(this.mAnimator == null) {
this.mAnimator = new ValueAnimator();
this.mAnimator.setInterpolator(new DecelerateInterpolator());
this.mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedOffset = (int) animation.getAnimatedValue();
mToolbar.offsetTopBottom(animatedOffset/2);
}
});
} else {
this.mAnimator.cancel();
}
this.mAnimator.setIntValues(0, mAppBarLayoutHeight);
this.mAnimator.start();
}
U could either do toolbar.transitionY(int y); on the on scroll method or use visibility gone and u have to add an header in the list view or recycler view with the size of the toolbar. So that the whole list still shows
Related
I'm trying to use a CoordinatorLayout with a BottomNavigationView, an AppBarLayout, and a ViewPager. Here is my layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="enterAlways|scroll"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:itemIconTint="?colorPrimaryDark"
app:itemTextColor="?colorPrimaryDark"
app:menu="#menu/navigation"/>
</android.support.design.widget.CoordinatorLayout>
The problem is that the CoordinatorLayout places the ViewPager to extend to the bottom of the screen, so the bottom is obscured by the BottomNavigationView, like this:
This happens even though the CoordinatorLayout itself doesn't extend down so far:
I've tried adding app:layout_insetEdge="bottom" to the BottomNavigationView and app:layout_dodgeInsetEdges="bottom" to the ViewPager, but that has a different problem: it shifts the bottom of the ViewPager up, but it keeps the same height, so the top is now chopped off:
I tried two other experiments. First, I tried removing the BottomNavigationView from the CoordinatorLayout and making them siblings under a vertical LinearLayout. Second, I put the ViewPager and BottomNavigationView together under a LinearLayout, hoping they would layout out correctly. Neither helped: in the first case, the CoordinatorLayout still sized the ViewPager with respect to the entire screen, either hiding part of it behind the BottomNavigationView or chopping off the top. In the second case, the user needs to scroll to see the BottomNavigationView.
How do I get the layout right?
P.S. When I tried the layout suggested by #Anoop S S (putting the CoordinatorLayout and the BottomNavigationView as siblings under a RelativeLayout), I get the following (with the ViewPager still extending down behind the BottomNavigationView):
As before, the CoordinatorView itself only extends down to the top of the BottomNavigationView.
I came up with a different approach (not battle tested yet though):
I subclassed AppBarLayout.ScrollingViewBehavior to adjust the bottom margin of the content view based on the height of the BottomNavigationView (if present). This way it should be future proof (hopefully) if the height of the BottomNavigationView changes for any reason.
The subclass (Kotlin):
class ScrollingViewWithBottomNavigationBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.ScrollingViewBehavior(context, attrs) {
// We add a bottom margin to avoid the bottom navigation bar
private var bottomMargin = 0
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
return super.layoutDependsOn(parent, child, dependency) || dependency is BottomNavigationView
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val result = super.onDependentViewChanged(parent, child, dependency)
if(dependency is BottomNavigationView && dependency.height != bottomMargin) {
bottomMargin = dependency.height
val layout = child.layoutParams as CoordinatorLayout.LayoutParams
layout.bottomMargin = bottomMargin
child.requestLayout()
return true
} else {
return result
}
}
}
And then in the layout XML you put:
app:layout_behavior=".ScrollingViewWithBottomNavigationBehavior"
instead of
app:layout_behavior="#string/appbar_scrolling_view_behavior"
Basically what you have to do is create a Relativelayout as parent and put BottomNavigationView and CoordinatorLayout as children. Then align BottomNavigationView at the bottom and set CoordinatorLayout above that. Please try the below code. It might have few attribute erros, because I wrote it here itself. And sorry for the messed up indentation.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/navigation"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="enterAlways|scroll"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:itemIconTint="?colorPrimaryDark"
app:itemTextColor="?colorPrimaryDark"
app:menu="#menu/navigation"/>
</RelativeLayout>
This is caused by app:layout_behavior="#string/appbar_scrolling_view_behavior" in your ViewPager. If you remove this line, you will see now it fits the CoordinatorLayout container (unfortunately, this includes now being underneath the Toolbar).
I found it helped to treat CoordinatorLayout as just a FrameLayout, with a few extra tricks. The app:layout_behavior attribute above is necessary to allow the toolbar to appear to scroll in and out... in reality, the layout is doing this by having the view linked to the collapsing toolbar (in your case, your ViewPager) be exactly a toolbar's height larger than the bounds. Scrolling up brings the view up to the bottom within the bounds, and pushes the toolbar up extending beyond the bounds. Scrolling down, vice versa.
Now, onto the BottomNavigationView! If, as I did, you want the BottomNavigationView visible the whole time, then move it outside the CoordinatorLayout, as Anoop said. Use CoordinatorLayout only for things that need to coordinate, everything else outside. I happened to use a ConstraintLayout for my parent view (you could use RelativeLayout or whatever works for you though). With ConstraintLayout, for you it would look like this:
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="enterAlways|scroll"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:itemIconTint="?colorPrimaryDark"
app:itemTextColor="?colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/navigation" />
</android.support.constraint.ConstraintLayout>
In Android Studio design view, you're still going to see the ViewPager appear to be larger than the container (probably looks like it's behind the Bottom Nav still). But that's ok, when you get to the bottom of the ViewPager's content, it will show (i.e. won't be behind the bottom navigation). This quirk in the design view is just the way the CoordinatorLayout makes the toolbar show/hide, as mentioned earlier.
I had a similar problem with a layout very close to OP's and a ViewPager with 3 pages but only page 2 and 3 which should be affected by appbar_scrolling_view_behavior.
After struggling for hours exploring dead-end possible solutions (layout_dodgeInsetEdges, Window insets, attempting to modify ViewPager's page measured size, android:clipChildren, fitSystemWindows, ...), I finally found an easy solution detailed below.
As Vin Norman explained, ViewPager overlapping BottomNavigation is entirely caused by appbar_scrolling_view_behavior set on the ViewPager. AppBarLayout will just make fullscreen the sibling that has appbar_scrolling_view_behavior. That's how it works.
If you only need this behavior on certain ViewPager pages, there is a simple fix than you can apply on the ViewPager's OnPageChangeListener to dynamically change the Behavior and add/remove required padding:
public class MyOnPageChangeListener extends ViewPager.SimpleOnPageChangeListener {
#Override
public void onPageSelected(int position) {
...
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) _viewPager.getLayoutParams();
if(position == 0) {
params.setBehavior(null);
params.setMargins(params.leftMargin, _appBarLayoutViewPagerMarginTopPx,
params.rightMargin, _appBarLayoutViewPagerMarginBottomPx);
} else {
params.setBehavior(_appBarLayoutViewPagerBehavior);
params.setMargins(params.leftMargin, 0, params.rightMargin, 0);
}
_viewPager.requestLayout();
}
}
For page at position 0 (the one we want the ViewPager to extend exactly below the Toolbar and above the BottomNavigationView), it removes the behavior and adds top and bottom padding, respectively _appBarLayoutViewPagerMarginTopPx and _appBarLayoutViewPagerMarginBottomPx that are constants easy to compute beforehand (respectively the value in pixel for R.attr.actionbarSize and the height for the NavigationBottomView. Usually both are 56dp)
For all other pages needing appbar_scrolling_view_behavior we restore the associated scrolling behavior (stored beforehand in _appBarLayoutViewPagerBehavior) and remove top and bottom padding.
I tested this solution and it works fine without caveat.
In case anyone is still searching for a solution of this problem:
Cause of the problem is that CoordinatorLayout is not calculating correctly size of AppBarLayout because it has Toolbar with app:layout_scrollFlags="enterAlways|scroll" setting. It thinks that Toolbar will hide when scrolling so it leaves all available space to ViewPager, but actually what happens is that toolbar shows so ViewPager moves down, behind NavigationBar.
Easiest way to solve this is just to add android:minHeight="?attr/actionBarSize" (or whatever toolbar height you are using) to AppBarLayout. This way CoordinatorLayout will know properly how much space it needs to leave for ViewPager.
If it still matters to someone:
In the answer of Anoop SS above, trying replacing the RelativeLayout with LinearLayout. Also set layout_height of CoordinatorLayout to 0dp and set layout_weight to 1.
I had almost the same problem....just that i wanted to have a static AdView at the bottom instead of the BottomNavigationView. Trying Anoop SS suggestions, at first, I got the same behaviour as OP: ViewPager extended behind the AdView. But then I did what I suggested about and everything worked fine.
Android layouts behave in weird manner or may be it is the lack of good documentation or the lack of knowledge on our part....but making a layout is just too annoying most of the time.
If you are using Androidx try this
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:layout_above="#+id/bottomNavView">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_nav" />
I have an app layout with a custom toolbar and a persistent BottomSheet - both inside of a CoordinatorLayout.
On a button click I want to show the BottomSheet. Right now the sheet is displayed fullscreen and overlays the toolbar. By setting the app theme to Theme.AppCompat.Light.DarkActionBar the BottomSheet stays below the ActionBar, but the bar cannot be customized.
Is there a way to limit the height of the persitent BottomSheet to fullscreen - ActionBar height?
This is my code in activity_main.xml
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:attrs="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.test.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:elevation="20dp"
android:elevation="20dp"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
app:elevation="20dp"
android:elevation="20dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
</LinearLayout>
<include layout="#layout/bottom_sheet_additem"/>
</CoordinatorLayout>
Here is the code of sheet_bottom.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/bottomSheetLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorAccent"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
android:fitsSystemWindows="true"
app:layout_behavior="#string/bottom_sheet_behavior">
<TextView
android:id="#+id/bottomsheet_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Lorem Ipsum Dolor..."
android:textColor="#FFFFFF" />
</RelativeLayout>
The image on the left hand side shows the BottomSheet which stops below the Toolbar - which is not working with my current code. Currently it looks like the picture on the right.
I had the same problem... I don't know if it's the best solution, but for now, worked for me.
Try to put your include inside another CoordinatorLayout in your activity_main.xml, with a marginTop like this:
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:attrs="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.test.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:elevation="20dp"
android:elevation="20dp"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
app:elevation="20dp"
android:elevation="20dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
</LinearLayout>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="56dp">
<include layout="#layout/bottom_sheet_additem"/>
</android.support.design.widget.CoordinatorLayout>
</CoordinatorLayout>
I hope it helps.
We can use app:layout_behavior instead of fixed height
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<include layout="#layout/bottom_sheet_additem"/>
</android.support.design.widget.CoordinatorLayout>
Your sheet_bottom should look like this
<?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:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/activity_main">
<RelativeLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:behavior_hideable="true"
app:behavior_peekHeight="?android:attr/actionBarSize"
app:elevation="#dimen/size_5dp"
app:layout_behavior="#string/bottom_sheet_behavior">
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The accepted answer works when you expand the bottom sheet to the full screen, but when collapsing it, it adds additional margin that makes a part of the collapsing layout hidden below the screen, so I decided to put the margin programmatically by listening to the collapse/hidden status of the BottomSheet.
First add the BottomSheet within a CoordintorLayout in xml
And add below callback listener.
mBottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
CoordinatorLayout bottomSheet = findViewById(..); // inflate the bottom sheet
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams();
if (newState == BottomSheetBehavior.STATE_COLLAPSED)
layoutParams.setMargins(0, 0, 0, 0); // remove top margin
else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
layoutParams.setMargins(0, 100, 0, 0); // add top margin
bottomSheet.setLayoutParams(layoutParams);
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
+1 for updating the margin programmatically. While the hidden part of the BottomSheet can be offset by increasing the peek height, using just the original solution above means the margin projects onto the underlying UI elements so the scroll action area projects off the actual BottomSheet onto the other UI.
Using the onStateChanged method in the BottomSheetCallback means acting on the event that the BottomSheet has already expanded or collapsed. Adding or removing the margin at this stage can result in seeing 'jerky' behaviour where for example the sheet momentarily reaches the fully expanded state covering the appbar before the margin is then programmatically applied to shift the UI components down resulting in a 'flash' as this applies.
Instead I used the onSlide method to detect when the BottomSheet was being slid up or down and added or removed the margins only once the sheet was half way through the transition. If the margins are applied too early in the slide motion then again the user can see the BottomSheet UI jumping up or down not long after initiating the action (they are less likely to notice this at the half way point if they have done a 'fling' up or down motion.
Also I found it worked best to fetch the height of the AppBar and the status bar and use those to set the required padding value for accurate placement in expanded mode.
This challenge can be avoided altogether by using a widget to trigger the BottomSheet state change programmatically.
#Override
public void onSlide(View bottomSheet, float slideOffset) {
boolean inRangeExpanding = oldOffSet < slideOffset;
boolean inRangeCollapsing = oldOffSet > slideOffset;
oldOffSet = slideOffset;
if (inRangeCollapsing && slideOffset < 0.5f) {
//reset padding on top of bottomsheet so there is no padding/overlap onto underlying sheet (which overlaps underlying sheet and so interfers with scrolling behaviour
bSheetView.setPadding(0,10,0,0);
Log.d(TAG,"onSlide STATE_COLLAPSING");
} else if(inRangeExpanding && slideOffset > 0.5f){
//reset padding on top of bottomsheet so there is padding/overlap onto underlying sheet so it does not write over the top of the menu appbar
bSheetView.setPadding(0, topMargin,0,0);
Log.d(TAG,"onSlide STATE_EXPANDING");
}
}
I have managed to figure out how to add a bottom toolbar to CoordinatorLayout, but can't seem to figure out how to hide the bottom RelativeLayout.
If I add #string/appbar_scrolling_view_behavior to the bottom RelativeLayout, the bottom bar appears when the user scroll upwards. The desired effect is both top and bottom bars appear when the user scrolls upwards. Any ideas how I would approach this? A universal bottom bar (for simple actions) across all the tab is needed since my ViewPager contain complicated code from other libraries.
A floating action button is not preferable since it hides the collection of actions inside a button that requires the user to tap and expand. Here below is my xml layout for managing the tabs and pager:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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="wrap_content"
xmlns:ads="http://schemas.android.com/tools">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<android.support.design.widget.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#1378BB"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<android.support.v7.widget.Toolbar
android:id="#+id/tabs"
android:background="#3202c4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
android:layout_alignParentBottom="true"
android:layout_marginBottom="100dp"
/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
Any thoughts or ideas? I tried placing the toolbar inside CollapsingToolbarLayout and use layout_alignParentBottom="true", but that only anchored the toolbar to AppBarLayout and not the entire CoordinatorLayout
Only if I could get #string/appbar_scrolling_view_behavior to trigger the other way around for the bottom RelativeLayout (make bar appear when scrolling in the opposite direction it was intended to trigger)
You need to add custom CoordinatorLayout.Behavior to the bottom Toolbar. Toolbar must be a direct child of CoordinatorLayout.
Mark it with:
app:layout_behavior="{name_of_the_class_of_behavior}"
app:layout_scrollFlags="scroll|enterAlways"
In custom behaviour override to methods:
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View fab, View dependency) {
return dependency instanceof AppBarLayout;
}
and the second one is:
public boolean onDependentViewChanged(CoordinatorLayout parent, View fab, View dependency)
where you control the visibility of the target view. Basically what is needed is to measure what part of AppBarLayout is shown, translate it and set it to your view accordingly:
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) view.getLayoutParams();
int viewBottomMargin = lp.bottomMargin;
int distanceToScroll = view.getHeight() + viewBottomMargin;
float ratio = dependency.getY() / toolbarHeight;
view.setTranslationY(-distanceToScroll * ratio);
More about it here
I can't find solution for such behaviour:
It is that header of the RecyclerView is a little bit under items. Of course I guess that it is RecyclerView.
How can I achieve that?
EDIT
What I have done is just adding decoration for recycler view.
This is my simple decorator:
public class HeaderItemDeceration extends RecyclerView.ItemDecoration {
public HeaderItemDeceration() {
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) == 0) {
outRect.bottom = -100;
}
}
}
It's working but the problem is that this header is disappearing too fast, I mean where next item is on the top, and under it there is header, and immediately is disappearing, because normally it should be hidden when next item is on the top.
EDIT 2
I haven't explain everything, so here I'm explaining.
In my case I don't want to have ActionBar. What I want is just image under RecyclerView like in example above, but without collapsing toolbar. Just let's say that my Activity has style which parent is Theme.AppCompat.Light.NoActionBar.
Taking into consideration my explanation and answers below I'm trying to reach the goal with such layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="fitXY"
android:src="#drawable/header"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_items"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:behavior_overlapTop="24dp"/>
</android.support.design.widget.CoordinatorLayout>
It's almost working. Almost, because I noticed unwanted effect, which is when I scroll to top sometimes I have to repeat scroll gesture to reach the top. I recorded it:
Bad effect recorded
I assume that with my goal using CollapsingToolbarLayout may be wrong.
I achieved this effect when I wrote the main home screen for the lawn care app for Scotts. Here is an image of how it looks.
This is accomplished by using a CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout, and RecyclerView. The key is a scrolling view behavior you need to set both app:behavior_overlapTop and app:layout_behavior="#string/appbar_scrolling_view_behavior" attributes on the RecyclerView (which only work if it's a sibling view of an AppBarLayout).
In my scenario, I had the header separate from the RecyclerView altogether. Depending on how your content is managed, this solution may not work for you (although it is a lot simpler for me to keep the header outside of the RV--one less viewtype/view holder to manage!)
The gist ends up looking like this:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<android.support.design.widget.AppBarLayout
android:layout_height="300dp" // fixed height of your header container
android:layout_width="match_parent"
android:fitsSystemWindows="true"
>
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" // snap/exitUntilCollapsed are optional. See more info on scroll flags here: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.LayoutParams.html#setScrollFlags(int)
>
<ImageView // This is where you'd put your header backdrop
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" // not essential, but most people want the parallax scrolling effect with their image header in this setup. this is how you would do it.
/>
</CollapsingToolbarLayout>
</AppBarLayout>
<RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:behavior_overlapTop="64dp" // This is what you're looking for!
/>
</CoordinatorLayout>
Its a combination of two view, not just a recycler view.
Android-ObservableScrollView will help you to reach what you want.
The trick is, there is a view in background and a recycler in front. The recycler view have a header which make the gap you want from top.
Whenever you scroll the recycler, you will get notified by the listener you used and in that you will scroll the bottom layout manually,
try all possible combinations of layout_scrollFlags of AppBarLayout it will help in improvement in scroll
app:layout_scrollFlags="scroll|snap" //try this it will work for you
app:layout_scrollFlags="scroll|exitUntilCollapsed" // ur current scroll
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
app:layout_scrollFlags="scroll|enterAlways"
and read scrolling-techniques-for-material-design
I have a RecyclerView in a LinearLayout. How can I have LinearLayout "slide" upwards until it hits the App Bar, whereby the RecyclerView should then begin scrolling the items as usual. Likewise, scrolling the list down will begin to "slide" the entire container down when the the first item is reached in the list until the container returns to its starting position.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/nearby_stops"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="#dimen/half_padding"
android:paddingTop="#dimen/half_padding"
android:scrollbars="none" />
</LinearLayout>
I looked at https://github.com/umano/AndroidSlidingUpPanel, however it does not support RecyclerView and the moment.
Put the RecyclerView inside a Fragment, so make an XML like so for the Fragment:
This is your XML for the RecyclerView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/nearby_stops"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="#dimen/half_padding"
android:paddingTop="#dimen/half_padding"
android:scrollbars="none" />
</LinearLayout>
Then for the Activity hosting the Fragment just add a FrameLayout for the Fragment:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Put other views here -->
<FrameLayout
android:id="#+id/slidingFragmentContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Then inside your Activity when you instantiate the Fragment do the following:
SampleFragment listFragment = new SampleFragment();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.abc_slide_in_bottom, R.anim.abc_slide_out_bottom,
R.anim.abc_slide_in_bottom, R.anim.abc_slide_out_bottom)
.addToBackStack(null)
.add(R.id.slidingFragmentContent, listFragment)
.commit();
The animations R.anim.abc_slide_in_bottom and R.anim.abc_slide_out_bottom are available in the Android API.
I also noticed you're not setting any orientation for your LinearLayouts. Set an orientation like so android:orientation="..."
You can assign a Button to show the Fragment, like so:
mButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
showFragment();
}
});
--------------------------------------------------------------------
To get the RecyclerView to scroll up as you scroll it just use the following:
Create an XML layout like this for your Activity:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="#dimen/map_view_height"
android:orientation="vertical"
android:fitsSystemWindows="true">>
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<YourCustomViewContainingTheMap
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
Inflate your Fragment inside the FrameLayout and then as you scroll up it should do a parallax animation on the Map. If you don't want a Parallax effect just set app:layout_CollapseMode="pin"
Here is a sample app I created, you can see as I scroll up on the RecyclerView it slides up:
(Please note the Frame Animations on GIFs is not that great)
To get a shadow behind the sliding list on your map, just set app:layout_collapseMode to parallax and then add another view in front of your MapView inside the CollapsingToolbarLayout which can be your mask, it can be be a simple view, and then you can adjust it's alpha value as you scroll up the list.
It's better to use the native android views at your disposal, I noticed that AndroidSlidingPanelLayout has around 43 issues.