Niklas
Niklas

Reputation: 25433

Disable expanding of AppBarLayout

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.

Upvotes: 15

Views: 13156

Answers (4)

Delark
Delark

Reputation: 1323

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>

Upvotes: 0

android developer
android developer

Reputation: 115990

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.

Upvotes: 16

Bilthon
Bilthon

Reputation: 2511

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

Upvotes: 26

Anne-Claire
Anne-Claire

Reputation: 872

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 !

Upvotes: 19

Related Questions