Need to disable expand on CollapsingToolbarLayout for certain fragments - android

I have an AppCompatActivity that controls replacing many fragments. Here is my layout for it.
activity_main.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<include layout="#layout/activity_main_frame"/>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="#color/white"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer"/>
</android.support.v4.widget.DrawerLayout>
activity_main_frame.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/backdrop"
android:layout_width="match_parent"
android:layout_height="256dp"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
<include layout="#layout/activity_main_items"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin"/>
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="bottom"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" >
</FrameLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:layout_anchor="#id/appbar"
app:layout_anchorGravity="bottom|right|end"
app:borderWidth="0dp"
android:src="#drawable/app_ic_slide_wallpaper_dark"
android:layout_margin="#dimen/big_padding"
android:clickable="true"/>
</android.support.design.widget.CoordinatorLayout>
My home fragment is set initially and that is where I want the collapsing toolbar expanded and that works fine. However when I change fragments from side drawer I want to disable the expanding toolbar.
I have figured out how to collapse it when a drawer item is selected but I also need to make sure it doesn't expand unless the home fragment is displayed. is this possible?
public void collapseToolbar(){
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appbar.getLayoutParams();
behavior = (AppBarLayout.Behavior) params.getBehavior();
if(behavior!=null) {
behavior.onNestedFling(coordinator, appbar, null, 0, 10000, true);
}
}

Disable nested scrolling on the scrolling fragment content:
recyclerView.setNestedScrollingEnabled(false);
Use this if you're using the support library:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);

This class will let you disable/re-enable the expansion behavior.
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled;
public DisableableAppBarLayoutBehavior() {
super();
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
public boolean isEnabled() {
return mEnabled;
}
}
Use it in your layout like so:
<android.support.design.widget.AppBarLayout
... other attributes ...
app:layout_behavior="com.yourpackage.DisableableAppBarLayoutBehavior"
>
<!-- your app bar contents -->
</android.support.design.widget.AppBarLayout>
Then, when you want to disable the behavior:
AppBarLayout myAppBar = ....;
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) myAppBar.getLayoutParams();
((DisableableAppBarLayoutBehavior) layoutParams.getBehavior()).setEnabled(false);

Now, in v23 of support library you can easily control your appbar visibility.
Just get a reference to your AppBarLayout and hide/show it depending on the fragment you want to load:
private AppBarLayout appBarLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
[...]
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
[...]
}
public void switchToFragment(Fragment fragment, String tag, boolean expandToolbar){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment = fragmentManager.findFragmentByTag(currentFragmentTag);
if(currentFragment == null || !TextUtils.equals(tag, currentFragmentTag) ){
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.replace(R.id.flContent, fragment, currentFragmentTag)
.commit();
if(expandToolbar){
appBarLayout.setExpanded(true,true);
}else{
appBarLayout.setExpanded(false,true);
}
}
}
P.S. don't forget to add the required dependencies in your build.gradle:
dependencies {
compile 'com.android.support:design:23.2.1'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:recyclerview-v7:23.2.1'
}
EDIT:
If you also want to lock your toolbar in certain fragments (apart from collapsing) you have to resort to workarounds as this feature is not provided by CollapsingToolbarLayout until now (v23.2.1 of support design). Here you can find my proposed workaround.

All you have to do is replace CoordinatorLayout with custom implementation of CoordinatorLayout which will cheat that nested scrolling has been handled.
MyCoordinatorLayout implementation:
public class MyCoordinatorLayout extends CoordinatorLayout {
private boolean allowForScroll = false;
public MyCoordinatorLayout(Context context) {
super(context);
}
public MyCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return allowForScroll && super.onStartNestedScroll(child, target, nestedScrollAxes);
}
public boolean isAllowForScroll() {
return allowForScroll;
}
public void setAllowForScroll(boolean allowForScroll) {
this.allowForScroll = allowForScroll;
}
}
activity view xml:
<LinearLayout
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:orientation="vertical"
>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--CONTENT-->
<com.example.views.MyCoordinatorLayout
android:id="#+id/coordinator"
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"
>
<com.example.views.ControllableAppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="192dp"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginBottom="32dp"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/primary"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.CollapsingToolbarLayout>
</com.example.views.ControllableAppBarLayout>
<FrameLayout
android:id="#+id/flContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</com.example.views.MyCoordinatorLayout>
<!-- DRAWER -->
<fragment
android:id="#+id/fDrawer"
android:name="com.example.fragment.DrawerFragment"
android:layout_width="#dimen/drawer_width"
android:layout_height="match_parent"
android:layout_gravity="left|start"
android:fitsSystemWindows="true"
android:clickable="true"
/>
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
I encourage you to use custom AppBarLayout implementation with helper methods to collapse/expand toolbar. On this gist you can find one.
Ok, now it's time to configure our toolbar in activity.
public class ToolbarAppcompatActivity extends AppCompatActivity
implements AppBarLayout.OnOffsetChangedListener {
protected Toolbar toolbar;
protected MyCoordinatorLayout coordinator;
protected ControllableAppBarLayout appbar;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
configureToolbar();
switchFragment(new FooFragment(), "FOO", true);
}
protected void configureToolbar() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
coordinator = (MyCoordinatorLayout) findViewById(R.id.coordinator);
appbar = (ControllableAppBarLayout) findViewById(R.id.appbar);
appbar.addOnOffsetChangedListener(this);
getDelegate().setSupportActionBar(toolbar);
}
public void switchToFragment(Fragment fragment, String tag, boolean expandToolbar){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment = fragmentManager.findFragmentByTag(currentFragmentTag);
if(currentFragment == null || !TextUtils.equals(tag, currentFragmentTag) ){
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.replace(R.id.flContent, fragment, currentFragmentTag)
.commit();
if(expandToolbar){
expandToolbar();
}else{
collapseToolbar();
}
}
}
protected void addFragment(Fragment fragment, String tag, boolean expandToolbar) {
FragmentManager fragmentManager = getSupportFragmentManager();
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.add(R.id.flContent, fragment, currentFragmentTag)
.addToBackStack(tag)
.commit();
if(expandToolbar){
expandToolbar();
}else{
collapseToolbar();
}
}
protected void collapseToolbar(){
appbar.collapseToolbar();
coordinator.setAllowForScroll(false);
}
public void expandToolbar(){
appbar.expandToolbar();
coordinator.setAllowForScroll(true);
}
}
Every time you want to switch fragment and collapse/expand toolbar just
call method switchFragment/addFragment with proper boolean parameter.
Just one last note. Make sure you use latest support libraries.
dependencies {
// android support
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
}
Do not use include tag in AppBarLayout. It does not work

The following code achieves 3 objectives:
Disable CollapsingToolbarLayout expand or collapse by the user, but still allow AppBarLayout.setExpanded.
Prevent scrolling of RecyclerView or NestedScrollView from expanding or collapsing the CollapsingToolbarLayout.
// scrollView can be RecyclerView or NestedScrollView
ViewCompat.setNestedScrollingEnabled(scrollView, false)
Prevent the user from expanding or collapsing the CollapsingToolbarLayout by flicking the AppBar.
val params = appBar.layoutParams as CoordinatorLayout.LayoutParams
if (params.behavior == null)
params.behavior = AppBarLayout.Behavior()
val behaviour = params.behavior as AppBarLayout.Behavior
behaviour.setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
override fun canDrag(appBarLayout: AppBarLayout): Boolean {
return false
}
})
https://code.luasoftware.com/tutorials/android/how-to-disable-or-lock-collapsingtoolbarlayout-collapse-or-expand/

With Android Design Library v23.1.1, the method described by #LucyFair does not work. I managed to get it to work by setting the app:layout_scrollFlags to enterAlwaysCollapsed only, and the appbar stays "locked".
Hope this helps. :)

I have used #JasonWyatt's solution and added DragCallback to behavior class to prevent touch and drag CollapsingToolbarLayout to expand it
private void setDragCallback() {
setDragCallback(new DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}

I have found a workaround solution that works with activity and various fragments. You implement the CollapsingToolbarLayout with AppBar etc in your activity and then each time you call a new fragment you can call these 2 functions.
When I want my appbar to keep collapsed:
public void lockAppBarClosed() {
mAppBarLayout.setExpanded(false, false);
mAppBarLayout.setActivated(false);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_height);
}
When I want my appbar to be expanded and scrollable again
public void unlockAppBarOpen() {
mAppBarLayout.setExpanded(true, false);
mAppBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_expand_height);
}
You can call thoses functions from your fragments by implementing an interface. Here's a quick example, for your case (toolbar expands only in homeFragment)
public interface CustomListener() {
void unlockAppBarOpen();
void lockAppBarClosed()
}
public class MainActivity extends BaseActivity implements CustomListener {
#Override
public void unlockAppBarOpen() {
mAppBarLayout.setExpanded(true, false);
mAppBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_expand_height);
}
#Override
public void lockAppBarClosed() {
mAppBarLayout.setExpanded(false, false);
mAppBarLayout.setActivated(false);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_height);
}
}
public class MainFragment extends BaseFragment {
#Override
public void onResume() {
super.onPause();
((MainActivity) getContext()).unlockAppBarOpen();
}
}
public class SecondFragment extends BaseFragment {
#Override
public void onResume() {
super.onPause();
((MainActivity) getContext()).lockAppBarClosed();
}
}
With this example:
each time the MainFragment will be displayed -> it will extends the toolbar and make it collapsable and expandable
each time the SecondFragment is displayed -> il will collapsed the toolbar to a standard size and prevent it to expand again
Hope it will help you !

I found Simple solution to enable/disable collapse in CollapsingToolbarLayout:
private void setExpandEnabled(boolean enabled) {
mAppBarLayout.setExpanded(enabled, false);
mAppBarLayout.setActivated(enabled);
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
if (enabled)
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
else
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
collapsingToolbarLayout.setLayoutParams(params);
}

I can't comment, so I will post my additions to JasonWyatt's DisableableAppBarLayoutBehavior solution as independent answer.
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled = true; // enabled by default
public DisableableAppBarLayoutBehavior() {
super();
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
if (!isEnabled()) return;
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if (!isEnabled()) return;
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
public boolean isEnabled() {
return mEnabled;
}
}
In addition to onStartNestedScroll also lock onNestedPreScroll and onNestedScroll itself to avoid unexpected behaviour. For example, in my case - calling setExpanded(false, true) on my app bar was braking expected behaviour and still was expanding with lags. Now it works:
LayoutParams layoutParams = (LayoutParams) context.appBarLayout.getLayoutParams();
((DisableableAppBarLayoutBehavior)layoutParams.getBehavior()).setEnabled(false);
context.appBarLayout.setLayoutParams(layoutParams);
context.appBarLayout.setExpanded(false, true); // collapse app bar

None of the provided solutions worked for me except this one. With this solution, i can easily manage the state of collapsing toolbar. This will prevent expanding of collapsing toolbar and set title for it.
public void lockAppBar(boolean locked,String title) {
if(locked){
appBarLayout.setExpanded(false, true);
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics());
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)appBarLayout.getLayoutParams();
lp.height = px;
appBarLayout.setLayoutParams(lp);
collapsingToolbarLayout.setTitleEnabled(false);
toolbar.setTitle(title);
}else{
appBarLayout.setExpanded(true, false);
appBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbarExpandHeight);
collapsingToolbarLayout.setTitleEnabled(true);
collapsingToolbarLayout.setTitle(title);
}
}

Find the AppBarLayout id as like this.
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
Disable expand on CollapsingToolbarLayout for certain fragments
appBarLayout.setExpanded(true,true);
Enable expand on CollapsingToolbarLayout for certain fragments
appBarLayout.setExpanded(false,true);
Hope it will help you !!

Locking and unlocking was not enough, simple lockings keeps the image shrinked; here my solution
Call this on resume that requires toolbar in collapsed mode.
private void lockAppBarClosed() {
appBarLayout.setExpanded(false,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(false);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
}
This is for a fragment that requires Snap
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(true);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
}
This is for a fragment requires scroll with SCROLL_FLAG_EXIT_UNTIL_COLLAPSED mode.
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(true);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
}
And the CustomAppBarLayoutBehavior.java
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean shouldScroll = false;
public CustomAppBarLayoutBehavior() {
super();
}
public CustomAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
return shouldScroll;
}
#Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
if(shouldScroll){
return super.onTouchEvent(parent, child, ev);
}else{
return false;
}
}
public void setScrollBehavior(boolean shouldScroll){
this.shouldScroll = shouldScroll;
}
public boolean isShouldScroll(){
return shouldScroll;
}
}
provide this behavior to AppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="210dp"
app:layout_behavior=".behaviors.CustomAppBarLayoutBehavior"
android:theme="#style/ThemeOverlay.MaterialComponents.ActionBar.Primary">

Thanks to #Desmond Lua's great answer I was finally able to fix this problem in my code. Here is my adapted solution using Java and Data Binding.
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
bind:expanded="#{myCondition? false : true}"
app:disableCollapsingScroll="#{myCondition? false : true}"
... >
<com.google.android.material.appbar.CollapsingToolbarLayout
... >
...
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:nestedScrollingEnabled="#{myCondition? false : true}"
... >
...
</NestedScrollView>
#BindingAdapter("disableCollapsingScroll")
public static void bindDisableCollapsingScroll(AppBarLayout appBarLayout, boolean disabled) {
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
if (params.getBehavior() == null) {
params.setBehavior(new AppBarLayout.Behavior());
}
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return disabled;
}
});
}

You can lock appbarlayout expansion by resetting collapsing toolbar height to toolbar height
toolbarHeight = toolbar.getLayoutParams().height;
if (expand) {
collapsingToolbar.getLayoutParams().height = getResources().getDimensionPixelOffset(R.dimen.collapsingToolbarDefaultHeight);
appBarLayout.setExpanded(true, true);
} else {
//collapse
//** it is important you do this before resetting **
appBarLayout.setExpanded(false, true);
appBarLayout.postDelayed(new Runnable() {
#Override
public void run() {
collapsingToolbar.getLayoutParams().height = toolbarHeight;
}
}, 700/* 600 is default animation time to collapse */);
}

The answer of #JasonWyatt works when dragging the scrolling view; but when dragging the appBarLayout it doesn't.
To fix this register a BaseDragCallback listener in the constructors of the custom class to return the mEnabled boolean in the callback:
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled;
public DisableableAppBarLayoutBehavior() {
super();
super.setDragCallback(new BaseDragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
super.setDragCallback(new BaseDragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout parent, #NonNull AppBarLayout child, #NonNull View directTargetChild, View target, int nestedScrollAxes, int type) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public boolean isEnabled() {
return mEnabled;
}
}

In order to lock the app bar just dynamically set the minHeight of the CollapsingToolbarLayout within it. That will prevent it from being collapsed when you drag on it.

Related

BottomNavigationView not hide on scrollview

iam new to android,please help me.
i want to hide bottom navigation bar so i am using bottom navigation behavior,
i have a scrollview when i scroll the bottomnavigationbar scrolls, i dosent hide please help me
morethan a week am trying to solve this issue please help me
MainActivity.java
mBottomNavigationView=findViewById(R.id.bottom_nav);
mBottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_nav);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mBottomNavigationView.getLayoutParams();
layoutParams.setBehavior(new BottomNavigationViewBehavior());
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.bottombaritem_map:
........
......
return true;
}
return false;
}
});
}
BottomNavigationViewBehavior
public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
private int height;
#Override
public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
height = child.getHeight();
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout,
BottomNavigationView child, #NonNull
View directTargetChild, #NonNull View target,
int axes, int type)
{
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull BottomNavigationView child,
#NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
#ViewCompat.NestedScrollType int type)
{
if (dyConsumed > 0) {
slideDown(child);
} else if (dyConsumed < 0) {
slideUp(child);
}
}
private void slideUp(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(0).setDuration(200);
}
private void slideDown(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(height).setDuration(200);
}
}
Layout xml file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView 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/main_book_scrol1"
android:layout_width="fill_parent"
android:visibility="visible"
android:layout_height="fill_parent"
android:fillViewport="true">
...........
..........
</ScrollView>
<!---your RecyclerView/Fragment Container Layout-->
<FrameLayout
android:id="#+id/container"
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/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:itemBackground="#color/white"
app:menu="#menu/bottombar_menu" />
</android.support.design.widget.CoordinatorLayout>
<!---NavigationView-->
</android.support.v4.widget.DrawerLayout>

Hide FAB in NestedScrollView when scrolling

I am having a nestedscrollview with content like some linearlayouts and textviews.
I am using a floatingactionbutton library for some reasons, as well. So I can't use any behavior for it.
I don't know how I should handle the scrollchangelistener from scrollview to hide and show the fab dynamically like a behavior.
Any suggestions how to hide and show the fab while scrolling?
Simple add this code below to your NestedScrollView ScrollChangeListener:
NestedScrollView nsv = v.findViewById(R.id.nsv);
nsv.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY > oldScrollY) {
fab.hide();
} else {
fab.show();
}
}
});
Create FabScrollBehavior class
public class FabScrollBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private int toolbarHeight;
public FabScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.toolbarHeight = AppUtil.getToolbarHeight(context);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
if (dependency instanceof AppBarLayout) {
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
int distanceToScroll = fab.getHeight() + fabBottomMargin;
float ratio = (float)dependency.getY()/(float)toolbarHeight;
fab.setTranslationY(-distanceToScroll * ratio);
}
return true;
}
}
Where AppUtil.getToolbarHeight(context) is -
public static int getToolbarHeight(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
}
then in your layout add to FloatingActionButton layout_behavior:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_accepted"
app:layout_behavior="pass.to.your.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
The whole layout looks like
<?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:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/Widget.AppTheme.AppBarOverlay">
<include
layout="#layout/include_layout_toolbar_scroll"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/include_layout_content_with_nestedscroll"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_accepted"
app:layout_behavior="pass.to.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
</android.support.design.widget.CoordinatorLayout>
Implemented from https://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling(part1)/
define variable type int in your Activity or fragment to set previous Scroll from ScrollView then use this method to listen change scroll in ScrollView Class
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// previousScrollY this variable is define in your Activity or Fragment
if (scrollView.getScrollY() > previousScrollY && floatingActionButton.getVisibility() == View.VISIBLE) {
floatingActionButton.hide();
} else if (scrollView.getScrollY() < previousScrollY && floatingActionButton.getVisibility() != View.VISIBLE) {
floatingActionButton.show();
}
previousScrollY = scrollView.getScrollY();
}
});
will work done all version of android
After spending such time i have found the solution for it.
It may work in all situations. Though it is a hack not the proper solution but you can apply it to make this thing work.
As we know setOnScrollChangeListener will only work if minimum api 23, so what if my minimum api level is less then 23.
So I found out solution from stack overflow that we can use getViewTreeObserver().addOnScrollChangedListener for that so this will be compatible solution for all devices.
Now let's move to the final solution of over problem "Hide fab button when nested scroll view scrolling and Show fab button when nested scroll view in ideal state"
So for that we can use Handler with postDelayed to slove this issue.
Define on variable in you context private int previousScrollY = 0;
Then use getViewTreeObserver().addOnScrollChangedListener to your nested scroll view like this.
NESTEDSCROLLVIEW.getViewTreeObserver().addOnScrollChangedListener(new
ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (NESTEDSCROLLVIEW.getScrollY() == previousScrollY) {
FABBUTTON.setVisibility(View.VISIBLE);
} else {
FABBUTTON.setVisibility(View.INVISIBLE);
}
}
}, 10);
previousScrollY = NESTEDSCROLLVIEW.getScrollY();
}
});
Now you are ready to go....
You can use this listener to observe and hide FAB when scrolling.
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (nestedScrollView != null) {
if (nestedScrollView.getChildAt(0).getBottom() <= (nestedScrollView.getHeight() + nestedScrollView.getScrollY())) {
fab.setVisibility(View.INVISIBLE);
} else {
fab.setVisibility(View.VISIBLE);
}
}
}
});

FAB has weird gap after add custom Coordinator Behavior on Pre-Lollipop

I am working with new FloatingActionButton and CoordinatorLayout in design support library. When I try to add custom Coordinator Behavior on FloatActionButton it work perfect on Lollipop+, but on Pre-Lollipop there are some weird gap margin on FloatActionButton
My main activity layout
<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_height="wrap_content"
android:layout_width="match_parent">
<android.support.v7.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" />
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="#android:drawable/ic_dialog_email"
app:layout_behavior="com.test.fabdemo.FloatingActionButtonBehavior" />
</android.support.design.widget.CoordinatorLayout>
with custom CoordinatorLayout behavior
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton button, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton floatingActionButton, View dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
int bottomMargin = lp.bottomMargin;
int distanceToScroll = floatingActionButton.getHeight() + bottomMargin;
float ratio = ViewCompat.getY(appBarLayout) / (float) appBarLayout.getTotalScrollRange();
ViewCompat.setTranslationY(floatingActionButton, -distanceToScroll * ratio);
return true;
}
return false;
}
}
Result in emulator 4.0.3 with and without behavior
After some research I found out that FAB's margin is managed by default Behavior, so make custom Behavior inherit from FloatActionButton.Behavior will solve the problem
public class FloatingActionButtonBehavior extends FloatingActionButton.Behavior {
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton button, View dependency) {
return dependency instanceof AppBarLayout || super.layoutDependsOn(parent, button, dependency);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton floatingActionButton, View dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
int bottomMargin = lp.bottomMargin;
int distanceToScroll = floatingActionButton.getHeight() + bottomMargin;
float ratio = ViewCompat.getY(appBarLayout) / (float) appBarLayout.getTotalScrollRange();
ViewCompat.setTranslationY(floatingActionButton, -distanceToScroll * ratio);
return true;
}
return super.onDependentViewChanged(parent, floatingActionButton, dependency);
}
}

Don't collapse Toolbar when RecyclerView fits the screen

I've made an app using Android Design Library, with a Toolbar and TabLayout. Actually 2 tabs are present, both with 2 RecyclerView, that automatically collapse the Toolbar when scrolled.
My question is: can I disable Toolbar collapsing when RecyclerView has few items and completely fits the screen (like in TAB 2)?
I've seen a lot of examples like CheeseSquare, made by a Google employee where the issue is still present: even if the RecyclerView has just 1 item, the toolbar keeps hiding on scroll.
I think I can just find out if the first item of the RecyclerView is visible on screen and if yes disable toolbar collapsing. The former is easy to implement, what about the latter?
This is my layout:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/coordinator_layout"
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"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
android:background="?attr/colorPrimary"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
<android.support.design.widget.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/glucosio_pink"
app:tabSelectedTextColor="#android:color/white"
app:tabIndicatorColor="#color/glucosio_accent"
app:tabTextColor="#80ffffff"/>
</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="wrap_content"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/main_fab"
android:layout_margin="16dp"
android:onClick="onFabClicked"
app:backgroundTint="#color/glucosio_accent"
android:src="#drawable/ic_add_black_24dp"
android:layout_gravity="bottom|right"
/>
</android.support.design.widget.CoordinatorLayout>
Final Solution (thanks MichaƂ Z.)
Methods to turn off/on Toolbar scrolling:
public void turnOffToolbarScrolling() {
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(null);
appBarLayout.setLayoutParams(appBarLayoutParams);
}
public void turnOnToolbarScrolling() {
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
//turn on scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(new AppBarLayout.Behavior());
appBarLayout.setLayoutParams(appBarLayoutParams);
}
Find out if last item of RecyclerView is visible in my Fragment.
If yes, disable scrolling:
public void updateToolbarBehaviour(){
if (mLayoutManager.findLastCompletelyVisibleItemPosition() == items.size()-1) {
((MainActivity) getActivity()).turnOffToolbarScrolling();
} else {
((MainActivity)getActivity()).turnOnToolbarScrolling();
}
}
RecyclerView now (since version 23.2) supports wrap_content. Just use wrap_content as the height.
You can check if the last item in RecyclerView is visible. If it's not then turn off scrolling programmatically using this method:
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(null);
appBarLayout.setLayoutParams(appBarLayoutParams);
I took a slightly different approach to solve this.
I created a custom AppBarBehavior that disables its self based on cells.
public class CustomAppBarBehavior extends AppBarLayout.Behavior {
private RecyclerView recyclerView;
private boolean enabled;
public CustomAppBarBehavior() {
}
public CustomAppBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
updatedEnabled();
return enabled && super.onInterceptTouchEvent(parent, child, ev);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
return enabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
return enabled && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updatedEnabled() {
enabled = false;
if(recyclerView != null) {
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
int count = adapter.getItemCount();
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null) {
int lastItem = 0;
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
lastItem = Math.abs(lastItems[lastItems.length - 1]);
}
enabled = lastItem < count - 1;
}
}
}
}
public void setRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
}
Then set the custom behavior on the app bar layout
appBarBehavior = new CustomAppBarBehavior();
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(appBarBehavior);
appBarLayout.setLayoutParams(appBarLayoutParams);
Last on page change of the view pager updated the RecyclerView on the behavior
private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
#Override
public void onPageSelected(final int position) {
appBarLayout.setExpanded(true, true);
appBarLayout.post(new Runnable() {
#Override
public void run() {
appBarBehavior.setRecyclerView(childFragments.get(position).getRecyclerView());
}
});
}
#Override
public void onPageScrollStateChanged(int state) { }
};
This should work with changing datasets.
Add this code after you change data in your adapter:
recyclerView.afterMeasured {
val isTurnedOff = recyclerView.turnOffNestedScrollingIfEnoughItems()
if (isTurnedOff) appBarLayout.setExpanded(true)
}
And this is the functions:
inline fun <T: View> T.afterMeasured(crossinline action: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
action()
}
})
}
fun RecyclerView.turnOffNestedScrollingIfEnoughItems(): Boolean {
val lm = (layoutManager as LinearLayoutManager)
val count = if (lm.itemCount <= 0) 0 else lm.itemCount - 1
val isFirstVisible = lm.findFirstCompletelyVisibleItemPosition() == 0
val isLastItemVisible = lm.findLastCompletelyVisibleItemPosition() == count
isNestedScrollingEnabled = !(isLastItemVisible && isFirstVisible)
return isNestedScrollingEnabled.not()
}
I guess it's the best solution.
You need to define your custom AppBarLayout behavior:
class CustomScrollingViewBehavior : AppBarLayout.Behavior {
constructor() : super()
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
var shouldScroll = true
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return shouldScroll && when (target) {
is NestedScrollView, is ScrollView, is RecyclerView -> {
return target.canScrollVertically(1) || target.canScrollVertically(-1)
}
else -> super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
}
}
And then you just need to use it in your layout as attribute of AppBarLayout:
...
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="56dp"
app:layout_behavior=".CustomScrollingViewBehavior">
...
That's it.
Note: custom behavior also supports full disabling of the scrolling - you just need to set shouldScroll flag to false
customScrollingViewBehavior.shouldScroll = false
If helps anyome in Kotlin based on correct answer, I did this for Kotlin:
fun changeToolbarScroll(isToScrolling: Boolean){
val params = toolbar.layoutParams as AppBarLayout.LayoutParams
val appBarLayoutParams = appBarLayout.layoutParams as
CoordinatorLayout.LayoutParams
params.scrollFlags = 0
toolbar.layoutParams = params
appBarLayoutParams.behavior = null
appBarLayout.layoutParams = appBarLayoutParams
if(isToScrolling){
params.scrollFlags =
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
toolbar.layoutParams = params
appBarLayoutParams.behavior = AppBarLayout.Behavior()
appBarLayout.layoutParams = appBarLayoutParams
}
}
In my case, I have a problem with a MainActivity that's manage navigation, toolbar and other shared things by 2 Fragments, the first Framgent use a RecyclerView and second is for show the deatil.
The problems is when I set a Menu and change MenuItem from MainAcitivity
It can sound silly and totally logical but remember Always make chages to the Menu or MenuItems before call supportFragmentManager.beginTransaction() when change fragments otherwise don't work, or dont update properly, no matter changes in .add, .replace(), show()...
fun showDetailImageFragment(searchImage: SearchImage) {
val searchFragment =
supportFragmentManager.findFragmentByTag(SEARCH_IMAGES)
changeToolbarScroll(false)
if (supportActionBar != null) {
supportActionBar!!.collapseActionView()
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.title = getString(R.string.detail_image_title)
}
actionSearch.isVisible = false
actionNighMOde.isVisible = false
actionAppSetings.isVisible = false
actionAbout.isVisible = false
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.animator.fade_in,
R.animator.fade_out,
R.animator.fade_in,
R.animator.fade_out
)
.hide(searchFragment!!)
.add(
R.id.frameLayout,
DetailImageFragment().newInstance(searchImage)
).addToBackStack(null)
.commit()
}
Just remove scroll from
app:layout_scrollFlags="scroll|enterAlways"
So it should be
app:layout_scrollFlags="enterAlways"
You can add within your XML, the property layout_behaviour with value #string/appbar_scrolling_view_behavior this way:
app:layout_behavior="#string/appbar_scrolling_view_behavior"
//turn off scrolling
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mToolbar.setLayoutParams(toolbarLayoutParams);

How to hide or show the partially hidden toolbar/actionbar when you stop scrolling the listview/recyclerview in android

So to show and hide the action bar when scrolling I am using this method
using coordinator layout and appbarlayout
But when I stop scrolling the list in the middle of toolbar being hidden or shown it stays there and only part of toolbar is visible.
What I want to do is make the toolbar show or hide completely based on the percentage of toolbar visible.
Is there a way I can acheive this using the coordinator layout and appbarlayout?
EDIT - Just get 23.1.0 design library and add "|snap" attribute to your ToolBar layout:
<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"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways|snap />
-----
-----
SO YOU DON'T NEED TO USE THE FOLLOWING CODE:
Have you tried to add the layout_behaviour attribute to AppBarLayout?
app:layout_behavior="AppBarLayoutSnapBehavior"
Then you need to create class "AppBarLayoutSnapBehavior":
public class AppBarLayoutSnapBehavior extends AppBarLayout.Behavior {
private ValueAnimator mAnimator;
private boolean mNestedScrollStarted = false;
public AppBarLayoutSnapBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
mNestedScrollStarted = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
if (mNestedScrollStarted && mAnimator != null) {
mAnimator.cancel();
}
return mNestedScrollStarted;
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
if (!mNestedScrollStarted) {
return;
}
mNestedScrollStarted = false;
int scrollRange = child.getTotalScrollRange();
int topOffset = getTopAndBottomOffset();
if (topOffset <= -scrollRange || topOffset >= 0) {
// Already fully visible or fully invisible
return;
}
if (topOffset < -(scrollRange / 2f)) {
// Snap up (to fully invisible)
animateOffsetTo(-scrollRange);
} else {
// Snap down (to fully visible)
animateOffsetTo(0);
}
}
private void animateOffsetTo(int offset) {
if (mAnimator == null) {
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
setTopAndBottomOffset((int) animation.getAnimatedValue());
}
});
} else {
mAnimator.cancel();
}
mAnimator.setIntValues(getTopAndBottomOffset(), offset);
mAnimator.start();
}
Should work for you.
I've got partial solution. To your recyclerview add onScrollListener bellow, with AppBarLayout in param
public class ImprovedScrollListener extends RecyclerView.OnScrollListener {
private WeakReference<AppBarLayout> mAppBarLayout;
public ImprovedScrollListener(AppBarLayout appBarLayout) {
mAppBarLayout = new WeakReference<>(appBarLayout);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mAppBarLayout.get() != null) {
if (!recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1)) {
/// TODO: 21.10.2015 handle this state
}else if(!recyclerView.canScrollVertically(-1)){
mAppBarLayout.get().setExpanded(true, true);
} else {
mAppBarLayout.get().setExpanded(false, true);
}
}
}
}
This will work for recyclerviews that can scroll, this still behaves ugly when recyclerview is not scrollable (see TODO in code). If somebody find solution for this case let me know
Setting Toolbar within AppBarLayout and cordinationLayout will itself take care of hiding and showing the actionBar
<?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:id="#+id/cordinationLayout"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.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/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

Categories

Resources