Android CollapsingToolbarLayout collapse Listener
Asked Answered
S

12

130

I am using CollapsingToolBarLayout alongside with AppBarLayout and CoordinatorLayout, and they are working Fine altogether. I set my Toolbar to be fixed when I scroll up, I want to know if there is a way to change the title text of the Toolbar, when CollapsingToolBarLayout it is collapsed.

Wrapping up, I want two different titles when scrolled and when expanded.

Thank you all in advance

Staton answered 28/7, 2015 at 16:38 Comment(0)
B
176

I share the full implementation, based on @Frodio Beggins and @Nifhel code:

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {

    public enum State {
        EXPANDED,
        COLLAPSED,
        IDLE
    }

    private State mCurrentState = State.IDLE;

    @Override
    public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE);
            }
            mCurrentState = State.IDLE;
        }
    }

    public abstract void onStateChanged(AppBarLayout appBarLayout, State state);
}

And then you can use it:

appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
    @Override
    public void onStateChanged(AppBarLayout appBarLayout, State state) {
        Log.d("STATE", state.name());
    }
});
Baldpate answered 24/11, 2015 at 10:55 Comment(6)
That's correct. But please not that using Proguard that enum is going to be translated in an integer value.Baldpate
Also enums are a very nice way of ensuring type safety. You can't have State.IMPLODED because it doesn't exist (the compiler would complain) but with Integer constants you could use a value that the compiler has no idea is wrong. They're good as singletons also but thats another story.Econah
@Econah for android enums check out IntDefHarr
public class State { public static final int EXPANDED = 1; public static final int COLLAPSED = 0; public static final int IDLE = 2; } private int mCurrentState = State.IDLE;Loraineloralee
@DavidDarias Personally I find enums a much cleaner approach even with their overhead (commence argument here... :-)Econah
in my case, add if (i == 0 && appBarLayout.getTotalScrollRange() != 0) to avoid state change to COLLAPSED then change immediately to EXPAND. Because after abs(i) = TotalScrollRange both i and TotalScrollRange are 0, so state change back to EXPAND.Crwth
W
109

This solution works perfectly for me to detect AppBarLayout collapsed or expanded.

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

            if (Math.abs(verticalOffset)-appBarLayout.getTotalScrollRange() == 0)
            {
                //  Collapsed


            }
            else
            {
                //Expanded


            }
        }
    });

Used addOnOffsetChangedListener on the AppBarLayout.

Wadding answered 26/10, 2016 at 13:0 Comment(1)
This fires collapsed on multiple occasions, also on expand.Azide
T
38

Hook a OnOffsetChangedListener to your AppBarLayout. When the verticalOffset reaches 0 or less than the Toolbar height, it means that CollapsingToolbarLayout has collapsed, otherwise it is expanding or expanded.

mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if(verticalOffset == 0 || verticalOffset <= mToolbar.getHeight() && !mToolbar.getTitle().equals(mCollapsedTitle)){
                    mCollapsingToolbar.setTitle(mCollapsedTitle);
                }else if(!mToolbar.getTitle().equals(mExpandedTitle)){
                    mCollapsingToolbar.setTitle(mExpandedTitle);
                }

            }
        });
Tomato answered 13/8, 2015 at 13:27 Comment(8)
it is not working for me. OnCollapse i want to enable the home button and on Expand hid the home buttonOgpu
The verticalOffset values seems to be zero when the toolbar is fully expanded, and then goes negative while collapsing. When the toolbar is collapsed, verticalOffset is equal to negative the toolbar height (-mToolbar.getHeight()). So... the toolbar is partially expanded "if (verticalOffset > -mToolbar.getHeight())"Donelu
In case anyone is wondering where the appBarLayout.getVerticalOffset() method is, you can call appBarLayout.getY() to retrieve the same value that's used in the callback.Cabezon
Unfortunately Jarett Millard isn't right. Depending on your fitsSystemWindow configuration and StatusBar configuration (transparent) the appBarLayout.getY() it may be that verticalOffset = appBarLayout.getY() + statusBarHeightParkins
setTitle in onOffsetChanged is giving me requestLayout() improperly called by CoordinatorLayoutAlp
Has anyone noticed if mAppBarLayout.addOnOffsetChangedListener(listener) is called repeatedly even if we are not actually interacting with the appbar? Or it's a bug in my layout / app where I am observing this behavior. Plz help!Granulate
Why do we have 2 options for listeners for it? Both BaseOnOffsetChangedListener and OnOffsetChangedListener . And one extends the other without adding anything... Also, is there any way to have a listener for the scroll-state of it, meaning that when the scroll started and when it becomes idle?Selfknowledge
This is exactly what I was looking for. Thank youVip
S
18

This code worked for me

mAppBarLayout.addOnOffsetChangedListener(new   AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (verticalOffset == -mCollapsingToolbarLayout.getHeight() + mToolbar.getHeight()) {
                //toolbar is collapsed here
                //write your code here
            }
        }
    });
Simplex answered 5/2, 2016 at 10:57 Comment(2)
Better answer than Nikola DespotoskiSeto
Seems to be not a reliable solution. I tested it and values on my device are the following: mCollapsingToolbarLayout.getHeight() = 1013, mToolbar.getHeight() = 224. So according to your solution verticalOffset in collapsed state must be -789, however it is equal to -693Stadtholder
L
17
private enum State {
    EXPANDED,
    COLLAPSED,
    IDLE
}

private void initViews() {
    final String TAG = "AppBarTest";
    final AppBarLayout mAppBarLayout = findViewById(R.id.appbar);
    mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        private State state;

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (verticalOffset == 0) {
                if (state != State.EXPANDED) {
                    Log.d(TAG,"Expanded");
                }
                state = State.EXPANDED;
            } else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
                if (state != State.COLLAPSED) {
                    Log.d(TAG,"Collapsed");
                }
                state = State.COLLAPSED;
            } else {
                if (state != State.IDLE) {
                    Log.d(TAG,"Idle");
                }
                state = State.IDLE;
            }
        }
    });
}
Lizettelizotte answered 23/10, 2015 at 12:31 Comment(0)
S
15

You can get collapsingToolBar alpha percentage using below :

appbarLayout.addOnOffsetChangedListener( new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            float percentage = ((float)Math.abs(verticalOffset)/appBarLayout.getTotalScrollRange());
            fadedView.setAlpha(percentage);
    });

For Reference : link

Spiritualist answered 20/6, 2017 at 6:30 Comment(2)
This is a great answer as it gives a normalized offset. In my opinion, the API should have provided this directly instead of the verticalOffset pixel distance.Statutable
I want when AppBarLayout collapsed then View is hide and expanded View is show. So I use val percentage = 1 - abs(verticalOffset).toFloat() / appBarLayout.totalScrollRange. It work very well for me.Vitiated
C
13

Here's a Kotlin solution. Add an OnOffsetChangedListener to the AppBarLayout.

Solution A:

Add AppBarStateChangeListener.kt to your project:

import com.google.android.material.appbar.AppBarLayout
import kotlin.math.abs

abstract class AppBarStateChangeListener : AppBarLayout.OnOffsetChangedListener {

    enum class State {
        EXPANDED, COLLAPSED, IDLE
    }

    private var mCurrentState = State.IDLE

    override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
        if (i == 0 && mCurrentState != State.EXPANDED) {
            onStateChanged(appBarLayout, State.EXPANDED)
            mCurrentState = State.EXPANDED
        }
        else if (abs(i) >= appBarLayout.totalScrollRange && mCurrentState != State.COLLAPSED) {
            onStateChanged(appBarLayout, State.COLLAPSED)
            mCurrentState = State.COLLAPSED
        }
        else if (mCurrentState != State.IDLE) {
            onStateChanged(appBarLayout, State.IDLE)
            mCurrentState = State.IDLE
        }
    }

    abstract fun onStateChanged(
        appBarLayout: AppBarLayout?,
        state: State?
    )

}

Add the listener to your appBarLayout:

appBarLayout.addOnOffsetChangedListener(object: AppBarStateChangeListener() {
        override fun onStateChanged(appBarLayout: AppBarLayout?, state: State?) {
            Log.d("State", state.name)
            when(state) {
                State.COLLAPSED -> { /* Do something */ }
                State.EXPANDED -> { /* Do something */ }
                State.IDLE -> { /* Do something */ }
            }
        }
    }
)

Solution B:

appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
        if (abs(verticalOffset) - appBarLayout.totalScrollRange == 0) { 
            // Collapsed
        } else if (verticalOffset == 0) {
            // Expanded
        } else {
            // Idle
        }
    }
)
Confectionery answered 4/12, 2019 at 16:49 Comment(0)
S
3

This solution is working for me:

@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
  if (i == 0) {
    if (onStateChangeListener != null && state != State.EXPANDED) {
      onStateChangeListener.onStateChange(State.EXPANDED);
    }
    state = State.EXPANDED;
  } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
    if (onStateChangeListener != null && state != State.COLLAPSED) {
      onStateChangeListener.onStateChange(State.COLLAPSED);
    }
    state = State.COLLAPSED;
  } else {
    if (onStateChangeListener != null && state != State.IDLE) {
      onStateChangeListener.onStateChange(State.IDLE);
    }
    state = State.IDLE;
  }
}

Use addOnOffsetChangedListener on the AppBarLayout.

Superscription answered 6/9, 2015 at 10:59 Comment(1)
Can you share your complete code? What is State.EXPANDED etc?Bur
G
2

This code is working perfect for me. You can use percentage scale How you like

@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
    double percentage = (double) Math.abs(verticalOffset) / collapsingToolbar.getHeight();
    if (percentage > 0.8) {
        collapsingToolbar.setTitle("Collapsed");
    } else {
        collapsingToolbar.setTitle("Expanded");
    }
}
Glyptography answered 6/7, 2017 at 17:21 Comment(0)
V
2

Here is the solution in Kotlin:

abstract class AppBarStateChangeListener : OnOffsetChangedListener {
    enum class State {
        EXPANDED, COLLAPSED, IDLE
    }

    private var mCurrentState =
        State.IDLE

    override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
        mCurrentState = if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED)
            }
            State.EXPANDED
        } else if (Math.abs(i) >= appBarLayout.totalScrollRange) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED)
            }
            State.COLLAPSED
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE)
            }
            State.IDLE
        }
    }

    abstract fun onStateChanged(
        appBarLayout: AppBarLayout?,
        state: State?
    )
}

Here is the listener:

  appbar.addOnOffsetChangedListener(object : AppBarStateChangeListener() {
        override fun onStateChanged(
            appBarLayout: AppBarLayout?,
            state: State?
        ) {
            if(state == State.COLLAPSED){
                LayoutBottom.visibility = View.GONE
            }else if(state == State.EXPANDED){
                LayoutBottom.visibility = View.VISIBLE
            }
        }
    })
Venesection answered 27/11, 2020 at 9:50 Comment(0)
C
1

If you are using CollapsingToolBarLayout you can put this

collapsingToolbar.setExpandedTitleColor(ContextCompat.getColor(activity, android.R.color.transparent));
collapsingToolbar.setTitle(title);
Chenoweth answered 3/4, 2017 at 18:0 Comment(0)
M
0

My Toolbar offset value get -582 when collapse, on expand=0 So I find value by setting offsetvalue in Toast & change code accordingly.

 mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if(verticalOffset == -582) {
            Toast.makeText(MainActivity.this, "collaped" + verticalOffset, Toast.LENGTH_SHORT).show();
            mCollapsingToolbarLayout.setTitle("Collapsed");
            }else if(verticalOffset == 0){
                Toast.makeText(MainActivity.this, "expanded" + verticalOffset, Toast.LENGTH_SHORT).show();
            mCollapsingToolbarLayout.setTitle("expanded");
            }
        }
    });
Mimicry answered 23/2, 2018 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.