14

I have a Toolbar together with a TabLayout and an image wrapped inside a CollapsingToolbarLayout. This gif pretty much sums it up:

https://raw.githubusercontent.com/vitovalov/TabbedCoordinatorLayout/master/art/demo.gif

This is the XML:

<?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:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapse_toolbar"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/header"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/photo"
                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="100dp"
                android:gravity="top"
                android:minHeight="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:titleMarginTop="15dp"/>

            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:layout_gravity="bottom"
                app:tabIndicatorColor="@color/colorAccent"/>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

What I want to achieve is that for instance on Tab 2 I don't want to let the user be able to see the image. So even if he tries to pull the AppBarLayout down he should not see the image under any circumstance.

Calling appBarLayout.setExpanded(false); upon showing Tab 2 does hide the image and collapse everything back. Though the moment you press on the AppBarLayout and swipe down you can get the image back. This should not be the case.

How can I prevent this behavior?

I'm using v23.1.1 of the support libraries. For version 22 of the libraryes there's already this question. The workaround is however no longer applicable with version 23.

Community
  • 1
  • 1
Niklas
  • 18,855
  • 28
  • 114
  • 153

4 Answers4

22

The trick provided by Anne-Claire works, but you end up loosing the animation. What I did instead was to create a custom Behavior extending the AppBarLayout.Behavior class that would allow me to enable/disable the scrolling whenever I chose.

public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {

    private boolean shouldScroll = false;

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

You should then apply this behavior to your AppBarLayout in the xml layout

<android.support.design.widget.AppBarLayout
    android:id="@+id/app_bar_layout"
    android:layout_width="wrap_content"
    android:layout_height="@dimen/activity_main_toolbar_height_extended"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:fitsSystemWindows="true"
    android:elevation="4dp"
    app:layout_behavior="io.eighttails.mvp.widgets.CustomAppBarLayoutBehavior">

And then you'll be able to switch it on and off as you please by doing:

CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(true);

or

CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(false);
Bilthon
  • 2,435
  • 8
  • 33
  • 44
18

I have found a trick!

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

Hope it will help you !

Anne-Claire
  • 807
  • 8
  • 16
  • this is pretty good - but I need two extra things - I need that the contracted bar shows the contracted text and that it should still be able to react to a tap... – Kibi Feb 21 '16 at 14:40
  • Note, if you have a transparent status bar, this will go under it. You'll need to add that into your offset also. – Advice-Dog Jan 18 '18 at 22:33
  • can you check my problem, i have similar problem. Here is my question link: https://stackoverflow.com/questions/50774518/appbarlayout-collapsingtoolbar-how-to-disable-expand-on-edittext-click – NoName Jun 09 '18 at 20:10
  • This is not working since the locked state shows the image not the title. – kelalaka Jan 30 '20 at 12:23
  • You also need to change [collapsing toolbar parameters](https://stackoverflow.com/questions/32418625/changing-toolbar-and-collapsingtoolbarlayout-scroll-flags-programmatically) to work. – kelalaka Jan 30 '20 at 14:53
12

Based on the answer of "Bilthon" (here), I think this solution is much shorter, in Kotlin:

//allows to block scrolling of AppBarLayout https://stackoverflow.com/a/48086783/878126
class BlockableAppBarLayoutBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.Behavior(context, attrs) {
    var isShouldScroll = false

    override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int) = isShouldScroll

    override fun onTouchEvent(parent: CoordinatorLayout, child: AppBarLayout, ev: MotionEvent) = isShouldScroll && super.onTouchEvent(parent, child, ev)

}

Usage:

disable changing its size when scrolling :

((app_bar.layoutParams as CoordinatorLayout.LayoutParams).behavior as BlockableAppBarLayoutBehavior).isShouldScroll = false

and enable:

((app_bar.layoutParams as CoordinatorLayout.LayoutParams).behavior as BlockableAppBarLayoutBehavior).isShouldScroll = true

In case you have a RecyclerView, and wish to block it from scrolling too, you can use this solution I've found too.

android developer
  • 106,412
  • 122
  • 641
  • 1,128
  • 3
    This did the trick for me, very short and clean. Remember to define the behaviour in the xml as well, inside your AppBarLayout: ` app:layout_behavior="yourpath.BlockableAppBarLayoutBehavior" ` else when calling layoutParams.behaviour you're gonna get a NPE – Adrian Coman Oct 11 '19 at 15:29
0

I'm sure that by now OP did solved his problem, but..

For those of you who haven't found an answer for your particular case, take in consideration (if you've been paying close attention) that NONE of the answers on any of the related questions that pertain to disabling this collapsing/scrolling behavior has worked WITHOUT a NestedScrollView.

As I assume is the same case for those of you who are using generic toolbars/layouts instead of an AppBarLayout (if that is even possible... may be, I don't know) for this collapsing functionality.

The particularity of this problem is that IF someone chooses NOT to disable the collapsing scroll, the combination of ViewPager's/RecyclerViews/etc.. work absolutely fine without the need to use a NestedScrollView..., BUT in the case you want to disable it, it won't be possible (as far as what I've read and tried)

What makes it somewhat worse, is that there are so many variables and so many combinations of these same variables on an XML/Kotlin/Java code, and at the same time all of them could very well end up arriving to a same look that may even give a similar behavior, that it makes it extremely easy to overlook a solution, or even the problem itself.

So maybe the solution is just to use a NestedScrollView...

@BindingAdapter("scrollable")
public static void setScrollable(@NotNull AppBarLayout appBarLayout, boolean scrollable) {

    CoordinatorLayout.LayoutParams layoutParams =
            (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
    AppBarLayout.Behavior layoutBehavior =
            (AppBarLayout.Behavior) layoutParams.getBehavior();

    AppBarLayout.Behavior finalBehavior =
            layoutBehavior == null? new AppBarLayout.Behavior() : layoutBehavior;
    finalBehavior
            .setDragCallback(
                    new AppBarLayout.Behavior.DragCallback() {
                        @Override
                        public boolean canDrag(@NonNull AppBarLayout appBarLayout) {

                            return scrollable;
                        }
                    }
            );
    if (layoutBehavior == null) {

        layoutParams.setBehavior(finalBehavior);
    }
}

@BindingAdapter("setNestedScrollViewScrollable")
public static void setNestedScrollViewScrollable(@NotNull NestedScrollView nestedScrollView, boolean scrollable) {

    nestedScrollView.setNestedScrollingEnabled(scrollable);
}

At the XML:

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/app_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            scrollable="@{false}"    //here
            android:theme="@style/Theme.PieceVolumeCalculator.AppBarOverlay"
            >


        <androidx.core.widget.NestedScrollView
            android:id="@+id/activity_main_nestedscrollview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            setNestedScrollViewScrollable="@{false}"    //here
            >
         // your ViewPager/ RecyclerView/ etc.. here
       </androidx.core.widget.NestedScrollView>
Delark
  • 209
  • 1
  • 2
  • 11