Hiding the ActionBar on RecyclerView/ListView onScroll
Asked Answered
D

7

46

In my application I got an activity with some kind of actionbar at the top and the listview below it. What I want to do - is to scroll it UP with the list, so it hides and then, when the list is being scrolled down - it should scroll down with the list, like it was just over the upper screen border. how can i achieve this functionality?

Dues answered 26/11, 2012 at 5:45 Comment(2)
#11494800Thermostatics
@Graykos, can you choose a correct answer?Factual
F
77

Updated 6/3/2015:

Google now supports this using the CoordinatorLayout.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_scrollFlags="scroll|enterAlways" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </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.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_done" />

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

Source: https://github.com/chrisbanes/cheesesquare/blob/master/app/src/main/res/layout/include_list_viewpager.xml

Documented here: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html

Original Answer:

Example similar to Google Play Music and Umano apps:

https://github.com/umano/AndroidSlidingUpPanel

Take a look at the code in this repository. As you slide the panel up, the ActionBar slides up as well.

From the Demo:

   getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);

   SlidingUpPanelLayout layout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
    layout.setShadowDrawable(getResources().getDrawable(R.drawable.above_shadow));
    layout.setAnchorPoint(0.3f);
    layout.setPanelSlideListener(new PanelSlideListener() {
        @Override
        public void onPanelSlide(View panel, float slideOffset) {
            Log.i(TAG, "onPanelSlide, offset " + slideOffset);
            if (slideOffset < 0.2) {
                if (getActionBar().isShowing()) {
                    getActionBar().hide();
                }
            } else {
                if (!getActionBar().isShowing()) {
                    getActionBar().show();
                }
            }
        }

        @Override
        public void onPanelExpanded(View panel) {
            Log.i(TAG, "onPanelExpanded");

        }

        @Override
        public void onPanelCollapsed(View panel) {
            Log.i(TAG, "onPanelCollapsed");

        }

        @Override
        public void onPanelAnchored(View panel) {
            Log.i(TAG, "onPanelAnchored");

        }
    });

Download example here:

https://play.google.com/store/apps/details?id=com.sothree.umano

enter image description here

ListView - without Libraries:

I recently wanted the same functionality and this works perfectly for me:

As the user scrolls upward, the ActionBar will be hidden in order to give the user the entire screen to work work with.

As the user scrolls downward and lets go, the ActionBar will return.

getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);

listView.setOnScrollListener(new OnScrollListener() {
    int mLastFirstVisibleItem = 0;

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {   }           

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    
        if (view.getId() == listView.getId()) {
            final int currentFirstVisibleItem = listView.getFirstVisiblePosition();

            if (currentFirstVisibleItem > mLastFirstVisibleItem) {
                // getSherlockActivity().getSupportActionBar().hide();
                getSupportActionBar().hide();
            } else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
                // getSherlockActivity().getSupportActionBar().show();
                getSupportActionBar().show();
            }

            mLastFirstVisibleItem = currentFirstVisibleItem;
        }               
    }
});

RecyclerView - without libraries

    this.mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        int mLastFirstVisibleItem = 0;

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            final int currentFirstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

            if (currentFirstVisibleItem > this.mLastFirstVisibleItem) {
                MainActivity.this.getSupportActionBar().hide();
            } else if (currentFirstVisibleItem < this.mLastFirstVisibleItem) {
                MainActivity.this.getSupportActionBar().show();
            }

            this.mLastFirstVisibleItem = currentFirstVisibleItem;
        }
    });

Let me know if you need anymore help!

Factual answered 20/7, 2013 at 23:30 Comment(30)
I like it. It seems to work, but there is an ugly flickering of the actionbar when it hides :-(Bummer
@hamen Yes, I have experienced the same issue. If you can find a better solution please let me know!Factual
@JaredBurrows ne'mi's answer explains what's causing the flickering and how to avoid it. I recommend adding a "header" view with the Action Bar padding.Whimper
my two androids >4.2 works without flickering, thanksOystercatcher
That's works fine with plain listview ( listview header + acrion bar overlay) what about if my listview inside viewpager fragment with pagerrabstips? Iguess i need something like header view but for viewpager. Any suggestions?Elane
@GeorgyGobozov I am not sure. I have used this method before with tabs just to hide the actionbar and keep the tabs similar to the google play store.Factual
@Jared Burrows did you use actionbar overlay and padding for this? (like in answer below) Or you just called actionBar.hide()?Elane
@GeorgyGobozov Yes, <item name="windowActionBarOverlay">true</item>.Factual
I used the plain listview solution, but hid my navigation tabs instead, with: if (currentFirstVisibleItem > mLastFirstVisibleItem) { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); } else if (currentFirstVisibleItem < mLastFirstVisibleItem) { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); } Not sure if there is a better way to do it, but it works nicely no flickering.Last
@Last Those tabs are deprecated.Factual
Thanks @JaredBurrows, I will add it to my todo list to replace them. The google training docs look like they haven't been updated yet. developer.android.com/training/implementing-navigation/…Last
what about if we have a SlidingTabLayout with a ViewPager? By using Actionbar Overlay we would hide the tabs!Lorant
@Lorant Yeah, post a solution for that!Factual
@JaredBurrows still can't find an easy one. I thought it would be something standard, since it's the play app and many others...Lorant
@Lorant Google always tends to add features in their own apps and the do not share their "special" tricks. This is why people create their own open source libraries on Github.Factual
I've tried that cheesesquare sample, but at the first layout (toolbar with tablayout) the toolbar doesn't hide when I scrolled up the list..someone know why?Swimmingly
@DarkLeonhart I would need to see your code. The toolbar needs to be in the AppLayout and in the CoordinatorLayout.Factual
@JaredBurrows Thanks, it worked already. The mistake is I'm still using recyclerview.jar from previous version (I'm still using Eclipse). The jar needs to be the latest version (v22).Swimmingly
Is there any way we can use the coordinator layout to also hide a bottom bar or the FAB ?Hines
@Lupsaa Have you tried to use the layout with app:layout_scrollFlags="scroll|enterAlways" or something similar for your needs?Factual
@Dalvikvm I posted that link in my answer.Factual
how to make it work with StaggeredGridLayoutManager ?Priestly
@VihaanVerma Did you even try anything? The code should be the same.Factual
@JaredBurrows I did but with staggered gird you an array of positions. The view on the first position is pretty big so the actionbar in my case won't hide till the very first time is up for recycling.Priestly
I am not sure. I have not used the StaggeredGridLayoutManager Factual
Please use addOnScrollListener(OnScrollListener) on RecyclerView because setOnScrollListener is deprecated.Nagpur
Jared, I'm using the Coordinator Layout and the rest inside just like you use in the first code example, and I've a fragment with a ListView inside Swipe Refresh Layout inside a Frame Layout.. But the toolbar is not hiding when I scroll down this list.. what could the error be?A
@JaredBurrows: I tried the latest version of your answer but somehow it does not work with ListView scrolling. Any ideas?Younts
@Younts I wrote this a while ago. Don't use ListView, use RecyclerView.Factual
Thanks @JaredBurrows. The reason why I am using ListView is because I need fast scrolling which I am unable to achieve on recycler view. I referred github.com/danoz73/RecyclerViewFastScroller but it is very buggy.Younts
H
11

You experience the flickering since by hiding/showing the ActionBar the available space for your content layout changes, which causes a relayout. With this the index of the first visible item's position changes as well (you can verify this by logging out mLastFirstVisibleItem and currentFirstVisibleItem.

You can cope with the flickering by letting the ActionBar overlay your content layout. To enable overlay mode for the action bar, you need to create a custom theme that extends an existing action bar theme and set the android:windowActionBarOverlay property to true.

With this you can eliminate the flickering but the action bar will overlay your listview's content. An easy solution to this is to set the listview's (or the root layout's) top padding to the action bar's height.

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?android:attr/actionBarSize" />

Unfortunately this will result in a constant padding at the top. A refinement of this solution is to add a header view to the list view which has the height of ?android:attr/actionBarSize (and remove the top padding set previously)

Holding answered 12/3, 2014 at 22:16 Comment(2)
If you are using ActionBarSherlock or AppCompat, use android:paddingTop="?attr/actionBarSize"Factual
I used your "A refinement" for my GridView. I forked and fixed a tiny lib. You can find it here: github.com/alter-ego/GridViewHeaderBummer
B
8

What you are looking for is called Quick Return pattern, applied to the Action Bar. Google IO 2014 app use exactly that. I use it in one of my apps, you can check the source code of that Google app to see how they got it. The BaseActivity class is where you have what you need, read the code and extract just that specific functionality. Enjoy coding! :)

Biting answered 14/10, 2014 at 23:2 Comment(3)
But how to do it with webview scroll ?Helsell
@PiyushKukadiya the implementation is almost the same, just define a scroll listener for the Webview, here you have a full axample about how to do it: #14753023Biting
I did this but now I can't make the layout larger when the actionbar is hidden, how do I smoothly make the layout fit the space the actionbar once tooEsdras
P
3

I am sure that it's not the best solution. But I haven't found a better one yet. Hope it will be helpfull.

    static boolean scroll_down;
    ...
    mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
                if (scroll_down) {
                    actionBar.hide();
                } else {
                    actionBar.show();
                }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 70) {
                //scroll down
                scroll_down = true;

            } else if (dy < -5) {
                //scroll up
                scroll_down = false;
            }
        }
    });
Palila answered 21/11, 2014 at 14:43 Comment(4)
Even I arrived at the same solution, but can't avoid flickering. Have you been able to remove the flickering caused by the show / hide?Otway
My last version was: if (dy > 40) { scroll_down = true; } else if (dy < -5) { scroll_down = false; }Palila
did anyone remove that flickering effect when we scroll slowly?Olivero
just animate it for smooth nessTopflight
G
1

I have a thought that it is a good idea.

listView.setOnScrollListener(new OnScrollListener() {
    int mLastFirstVisibleItem = 0;

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
      switch (scrollState) {
            case OnScrollListener.SCROLL_STATE_FLING:
                if (view.getId()==lv_searchresult_results.getId()){
                final int currentFirstVisibleItem=lv_searchresult_results.getFirstVisiblePosition();
                    if(currentFirstVisibleItem>mLastFirstVisibleItem){

                    ObjectAnimator.ofFloat(toolbar, "translationY", -toolbar.getHeight()).start();
                    else if(currentFirstVisibleItem<(mLastFirstVisibleItem)){

                    ObjectAnimator.ofFloat(toolbar, "translationY", 0).start();
                    }
                    mLastFirstVisibleItem= currentFirstVisibleItem;
                }

                if(lv_searchresult_results.getLastVisiblePosition() == myAdapter.getListMap().size()){ 
                    if(myAdapter.getListMap().size() < allRows&&!upLoading){

                    }
                }
                break;
            case OnScrollListener.SCROLL_STATE_IDLE:

                if (view.getFirstVisiblePosition()<=1){
                    ObjectAnimator.ofFloat(toolbar, "translationY", 0).start();

                }
                    if(lv_searchresult_results.getLastVisiblePosition() == myAdapter.getListMap().size()){
                    if(myAdapter.getListMap().size() < allRows&&!upLoading){


                    }
                }
                break;
Gear answered 17/5, 2015 at 8:18 Comment(0)
S
0

I think it will work fine.....

listView.setOnScrollListener(new OnScrollListener() {
            int mLastFirstVisibleItem = listView.getLastVisiblePosition();
            boolean isScrolling = false;

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if(scrollState == 0)
                    isScrolling = false;
                else if(scrollState == 1)
                    isScrolling = true;
                else if(scrollState == 2)
                    isScrolling = true;     
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    
                if (view.getId() == myTeamListView.getId()) {

                    if(isScrolling) {
                    final int currentFirstVisibleItem = myTeamListView.getLastVisiblePosition();

                    if (currentFirstVisibleItem > mLastFirstVisibleItem) {                      
                        ((AppCompatActivity)getActivity()).getSupportActionBar().hide();
                    } else if (currentFirstVisibleItem < mLastFirstVisibleItem) {                       
                        ((AppCompatActivity)getActivity()).getSupportActionBar().show();                    
                    }
                    mLastFirstVisibleItem = currentFirstVisibleItem;
                    }
                }
            }
        });
Stratton answered 8/3, 2016 at 13:21 Comment(0)
S
0

If you are using a CoordinatorLayout, here is the trick.

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    app:layout_scrollFlags="scroll|enterAlways" />

The app:layout_scrollFlags="scroll|enterAlways" line will cause our Toolbar to scroll off the screen when the user scrolls down the list and as soon as he starts scrolling up the Toolbar will show up again.

Synthesize answered 14/3, 2017 at 8:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.