Need to disable expand on CollapsingToolbarLayout for certain fragments

54,191

Solution 1

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.

Solution 2

Disable nested scrolling on the scrolling fragment content:

recyclerView.setNestedScrollingEnabled(false);

Use this if you're using the support library:

ViewCompat.setNestedScrollingEnabled(recyclerView, false);

Solution 3

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);

Solution 4

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

Solution 5

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/

Share:
54,191
BigDX
Author by

BigDX

Updated on September 04, 2021

Comments

  • BigDX
    BigDX over 2 years

    I have a 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);
            }
        }