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

Sachin Bahukhandi
  • 2,378
  • 20
  • 29
Anaximandro Andrade
  • 1,429
  • 2
  • 11
  • 9

12 Answers12

175

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());
    }
});
rciovati
  • 27,603
  • 6
  • 82
  • 101
  • 22
    That's correct. But please not that using Proguard that enum is going to be translated in an integer value. – rciovati Mar 20 '16 at 01:21
  • 2
    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. – droppin_science May 09 '17 at 16:24
  • @droppin_science for android enums check out [IntDef](https://guides.codepath.com/android/Replacing-Enums-with-Enumerated-Annotations) – David Darias Dec 24 '17 at 02:37
  • 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; – Sjd Jan 10 '18 at 18:17
  • 1
    @DavidDarias Personally I find enums a much cleaner approach even with their overhead (commence argument here... :-) – droppin_science Mar 15 '18 at 13:08
  • 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`. – neo Jan 20 '22 at 08:40
108

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.

Bugs Happen
  • 2,169
  • 4
  • 33
  • 59
Muhamed Riyas M
  • 5,055
  • 3
  • 30
  • 31
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);
                }

            }
        });
Felix
  • 128
  • 1
  • 12
Nikola Despotoski
  • 49,966
  • 15
  • 119
  • 148
  • 1
    it is not working for me. OnCollapse i want to enable the home button and on Expand hid the home button – Maheshwar Ligade Sep 02 '15 at 07:04
  • 9
    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())" – Mike Nov 03 '15 at 00:00
  • 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. – Jarett Millard Mar 31 '16 at 21:13
  • 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() + statusBarHeight` – Capricorn Apr 06 '16 at 13:46
  • setTitle in `onOffsetChanged` is giving me `requestLayout() improperly called by CoordinatorLayout` – ono Jul 11 '16 at 03:30
  • 1
    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! – Rahul Shukla Aug 24 '16 at 09:59
  • 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? – android developer Sep 22 '19 at 07:39
  • This is exactly what I was looking for. Thank you – Lorenzo Morelli May 12 '21 at 09:54
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
            }
        }
    });
SAI
  • 181
  • 1
  • 6
  • Better answer than Nikola Despotoski – GOBINATH.M Mar 07 '17 at 02:26
  • 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 -693 – Leo DroidCoder Apr 03 '17 at 14:48
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;
            }
        }
    });
}
Naveed Ahmad
  • 6,627
  • 2
  • 58
  • 83
terrakok
  • 266
  • 3
  • 3
14

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

Naveen Kumar M
  • 7,497
  • 7
  • 60
  • 74
  • 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. – dbm Jul 25 '17 at 12:26
  • 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. – PhongBM May 21 '21 at 02:09
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
        }
    }
)
James
  • 4,573
  • 29
  • 32
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.

Nifhel
  • 2,013
  • 2
  • 26
  • 39
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");
    }
}
Artur Gniewowski
  • 470
  • 7
  • 17
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
            }
        }
    })
Elletlar
  • 3,136
  • 7
  • 32
  • 38
1

If you are using CollapsingToolBarLayout you can put this

collapsingToolbar.setExpandedTitleColor(ContextCompat.getColor(activity, android.R.color.transparent));
collapsingToolbar.setTitle(title);
Irving Dev
  • 389
  • 2
  • 8
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");
            }
        }
    });
Mazhar Ali
  • 111
  • 2
  • 6