How to reset the Toolbar position controlled by the CoordinatorLayout?
Asked Answered
A

9

55

The app I'm working on consists of a Navigation Drawer which is implemented in an Activity. The activity layout is as follows:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinator"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <include
            android:id="@+id/appbar"
            layout="@layout/appbar" />

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

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_drawer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/header_drawer"
        app:menu="@menu/menu_nav">
    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>

</FrameLayout>

This is a very common pattern, only thing that changes frequently is the Fragment inside the container layout.

If any of the Fragment has a scrolling element, upon scrolling, the CoordinatorLayout will happily make position translations, including the Toolbar/AppBarLayout.

The real problem here is, when the Fragment gets replaced, the position for the Toolbar remains the same, i.e., if the Toolbar is hidden, it will stay so which isn't intended.

Result is this:

This:

After scroll

Gets stuck:

Then change

How can one reset the Toolbar position for this case?

EDIT: A bug is probable, the AppBarLayout offset change listener gets called only when relaunching the app (press back button and open the app), and stops getting called again after an intense fling.

Alderney answered 31/5, 2015 at 7:13 Comment(6)
no not yet, a kind person had answered here, he deleted his post before even I had chance to discuss, he said that he used setTranslationY() on AppBarLayout to manipulate it rather than reset, and mentioned that it should work, which didn't for me, still looking for answers to this. I'll definitely post a solution once I find it.Alderney
Hey, @razzledazzle, I am trying to put a fragment with recyclerview in a FrameLayout which is part of the CoordinatorLayout. I specified layout_behavior in FrameLayout, but the AppBarLayout does not scroll. Anything you are doing differently? Do I have to initialize views in my activity?Drislane
fixed it. Forgot to add app:layout_scrollFlags="scroll" to toolbar.Drislane
One more thing. Even if I have no items in the recyclerview, toolbar still scrolls. Any idea how to fix that?Drislane
@Drislane Make your Activity or Fragment implement AppBarLayout.OnOffsetChangedListener then, mAppBar.addOnOffsetChangedListener(this); and @Override public void onOffsetChanged(AppBarLayout appBarLayout, int offset) { if(recycler.canScrollVertically(1)) { appBarLayout.setTranslationY(0); } } Basically check if your scrolling view can scroll and lock the AppBarLayout.Alderney
maybe the problem is related to: https://mcmap.net/q/138249/-coordinatorlayout-messing-up-seterror-popup-positionDanonorwegian
H
48

To reset the scroll state, just get the AppBarLayout.Behavior object

CoordinatorLayout coordinator = (CoordinatorLayout) findViewById(R.id.coordinator);
AppBarLayout appbar = (AppBarLayout) findViewById(R.id.appbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appbar.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();       

and call onNestedPreScroll method manually:

int[] consumed = new int[2];
behavior.onNestedPreScroll(coordinator, appbar, null, 0, -1000, consumed);

If you would like to reset smoothly with an animation, you can try calling onNestedFling instead:

behavior.onNestedFling(coordinator, appbar, null, 0, -1000, true);
Hazelton answered 9/6, 2015 at 10:25 Comment(7)
For me it only works for collapsing AppBarLayout by setting positive 'velocityY' value. Above code with negative 'velocityY' value has no effect. I'm calling it from NestedScrollView's onTouchListener after ACTION_UP action. I want to implement snap behavior - to always collapse or expand AppBarLayout.Decalescence
Thanks for the answer, this is a good solution, I used the onNestedFling() inside the drawer open callback and it works pretty nicely.Alderney
I have exactly the same experience like @theb1uro. Only work for collapse with positive 'velocityY' value. Expand with negative 'velocityY' value has no effect...Knur
I tested it also on cheesesquare and the same result, any idea why this work only when collapsing AppBarLayout?Knur
params.getBehavior() is returning null. In xml, layout_behaviour tag is set on sibling NestedScrollView, not the AppBarLayout. Please help!Harmonia
Works nicely with ViewPager+TabLayout inside CoordinatorLayoutSherlock
negative 'velocityY' value will have effect, when you set app:layout_scrollFlags="scroll|enterAlways" ,Hal
I
40

First get a AppBarLayout refrence in you MainActivity, then in the pause state of the fragment that is being replaced, use the method below to expand toolbar :

MainActivity.appbar.setExpanded(true,true);

And or to close the toolbar :

MainActivity.appbar.setExpanded(false,true);

The second parameter is used to scroll the toolbar smoothly.

Isocrates answered 23/10, 2015 at 11:59 Comment(3)
This does not cover the use case when you would want to lock the AppBarLayout.Alderney
AppBarLayout has no such method setExpanded?Masquerade
@MuhammadBabar Update your dependencies, bro.Kynan
Q
14

Update your support lib to v23 then you can use:

appBarLayout.setExpanded(true/false);

public void setExpanded (boolean expanded)

Sets whether this AppBarLayout is expanded or not, animating if it has already been laid out.

As with AppBarLayout's scrolling, this method relies on this layout being a direct child of a CoordinatorLayout.

expanded true if the layout should be fully expanded, false if it should be fully collapsed

Quamash answered 3/12, 2015 at 14:57 Comment(1)
This fails to lock the AppBarLayout in place. It however does work for regular cases.Alderney
P
3

@razzledazzle The AppBarLayout stores onOffsetChangedListeners as WeakReferences, which means they are garbage collected when needed, for instance when you do a intense fling. See solution here:

https://code.google.com/p/android/issues/detail?id=176328

Preceptive answered 10/6, 2015 at 14:26 Comment(2)
Ah, this was definitely the case, I did see WeakReferences in the source but never thought how easily they could get GCed. This also explains why referencing the listeners as field under onResume() worked better as opposed to initializing anonymous classed during onCreate(). Implementing the listener as a class is a guaranteed solution as mentioned in the link. Thank you very much for your input!Alderney
According to that post is has been fixed.Shakitashako
P
2

I'm using this codes before fragment changes.

scrollingElement.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
scrollingElement.dispatchNestedPreScroll(0, -Integer.MAX_VALUE, null, null);
scrollingElement.stopNestedScroll();
Positive answered 7/6, 2015 at 16:43 Comment(2)
Can you add some explanations?Hermelindahermeneutic
Idea appears to be coming from the doc for startNestedScroll(), see developer.android.com/reference/android/view/…Nowicki
P
2

Replace or Wrap the FrameLayout inside the android.support.design.widget.CoordinatorLayout with a android.support.v4.widget.NestedScrollView and that will do the job automatically without having to any other hacks....so it would look like this:

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <include
        android:id="@+id/appbar"
        layout="@layout/appbar" />

</android.support.design.widget.CoordinatorLayout>
Pacemaker answered 8/5, 2017 at 15:8 Comment(0)
B
2

As document said

AppBarLayout supports
void setExpanded (boolean expanded, boolean animate);
Sets whether this AppBarLayout is expanded or not.

  • expanded boolean: true if the layout should be fully expanded, false if it should be fully collapsed
  • animate boolean: Whether to animate to the new state

So first, you need

AppBarLayout appBarLayout = findViewById(R.id.appBarLayout);

then to expand layout

appBarLayout.setExpanded(true, true); // with animation
appBarLayout.setExpanded(true, false); // without animation  

and to collapse layout

appBarLayout.setExpanded(false, true); // with animation
appBarLayout.setExpanded(false, false); // without animation 
Brubaker answered 14/2, 2018 at 17:20 Comment(0)
H
0
// Get the reference for appbar from layout
AppBarLayout appbar = rootView.findViewById(R.id.appbar);
//boolean expanded, boolean animate
appbar.setExpanded(true, true);
Herewith answered 20/12, 2019 at 3:35 Comment(0)
S
0

I had a similar problem. My layout was placed either higher than needed (like here), or lower, when I dynamically resized it. It was a Fragment that was replaced in FrameLayout programmatically. What I did: I began to re-creating my Fragment on change the FrameLayout size.

myFragment = new MyFragment();
getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_layout, myFragment)
                .commit();
Stelu answered 23/4, 2021 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.