10

I have made the snapping app bar like this:

enter image description here

Please note that when the scroll is left in the middle(i.e the title is half visible, then the app bar snaps automatically)

In case of google play this is what the snap looks like:

enter image description here

Now, I want the snap to work like the one in google play. Which is that when the snap occurs, then only the app bar should snap and the recycler view should not move. It would be better if the solution supported pre lollipop devices too.

Thanks!

penduDev
  • 4,743
  • 35
  • 37
  • http://stackoverflow.com/questions/30575293/coordinatorlayout-hiding-showing-half-visible-toolbar – Mateusz Pryczkowski Nov 16 '15 at 14:40
  • I have already used the snap flag...but my question is that when the snap happens, then the listview/recyclerview should not scroll with the snap (like in google play, only the appbar moves and the list items are stationary) – penduDev Nov 16 '15 at 14:43
  • Perhaps you use wrong layout hierarchy. Try [this answer](http://stackoverflow.com/questions/31839173/how-to-make-the-toolbar-snap-into-view-or-out-of-view-when-using-google-design-l/33163714#33163714) to make things work. – aleien Nov 20 '15 at 16:05
  • Did you find any effective solution? The proposed solutions are not achieving the *exact* behavior of the Google Play app. In the google play when the snap occurs, the scrollview is not moving, but just the toolbar is moving. Thanks – godness Feb 15 '16 at 14:09
  • nope...answer is still out there – penduDev Feb 15 '16 at 14:43

4 Answers4

6

See my library Retractable Toolbar

You have to add this on build.gradle

compile 'it.michelelacorte.retractabletoolbar:library:1.0.0'

Than in your MainActivity.java use RecyclerView and this:

RetractableToolbarUtil.ShowHideToolbarOnScrollingListener showHideToolbarListener;
recyclerView.addOnScrollListener(showHideToolbarListener = new RetractableToolbarUtil.ShowHideToolbarOnScrollingListener(toolbar));

if (savedInstanceState != null) {
            showHideToolbarListener.onRestoreInstanceState((RetractableToolbarUtil.ShowHideToolbarOnScrollingListener.State) savedInstanceState
                    .getParcelable(RetractableToolbarUtil.ShowHideToolbarOnScrollingListener.SHOW_HIDE_TOOLBAR_LISTENER_STATE));
}

This is effect:

enter image description here

EDIT:

Since 23.1.0 design library you can add |snap attribute to your ToolBar layout:

       <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlways|snap />

It should be more or less what you are looking.

Michele Lacorte
  • 5,323
  • 7
  • 32
  • 54
  • it doesn't work for me...when i add your library code with the tab strip, then this is what it looks like http://i.imgur.com/gPZADmo.gif – penduDev Nov 21 '15 at 08:04
  • I'm already using the snap flag...see the 2nd comment on the question Even with using the snap flag, the recycler view scrolls – penduDev Nov 21 '15 at 11:33
  • @penduDev what means recyclerView scrolls?.. explain it – Michele Lacorte Nov 21 '15 at 11:41
  • ok...when we leave the app bar half visible...then the app bar will snap....now, there are two ways snap can happen: 1. the list of items will move up or down with the snap. 2. the list of items will not move at all(just the app bar moves)...compare the 2 gifs in the question for clarity – penduDev Nov 21 '15 at 11:45
  • Yes, so you would like something like my gif just to be with the tabLayout...right? – Michele Lacorte Nov 21 '15 at 11:51
  • yes thats correct...also my minimum api level is 15..your lib supports only 21+ – penduDev Nov 21 '15 at 11:53
  • looks you 'll be honest, I'm trying 2 days to operate the tablayout the toolbar but I always some troubles layout, nonsense, but still not nice to see... @penduDev – Michele Lacorte Nov 22 '15 at 11:09
2

I found a solution that works quite well in my project. It consists of 2 behaviors, one for the AppBarLayout and another one for the scrolling container. You can find it on Github here: appbar-snap-behavior

It's quite easy to install it:

compile "com.github.godness84:appbar-snap-behavior:0.1.1"

Remember to add maven { url "https://jitpack.io" } in your root build.gradle at the end of repositories:

allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
}

Then:

  1. add app:layout_behavior="com.github.godness84.appbarsnapbehavior.AppBarSnapBehavior" to your AppBarLayout
  2. use app:layout_behavior="com.github.godness84.appbarsnapbehavior.ScrollingViewBehavior" into your scrolling container.

Unfortunately, since the default behavior of the AppBarLayout is replaced, some features are not available anymore (for example AppBarLayout.setExpanded()), but in normal situations it works! Give it a try and let me know.

godness
  • 134
  • 1
  • 8
  • Thank you, it does work very well. There is one thing however: When you are at the bottom of the (in my case) NestedScrollView and fling down, so that the view scrolls to the top, it doesn't scroll all the way up but stops at the height of the AppBarLayout. You then have to manually scroll again by the height of the AppBarLayout to get all the way to the top of the NestedScrollView. Is there a way to make it scroll all the way to the top on a fling action? @godness – Bernd Kampl Jun 13 '16 at 15:09
  • I fixed the behavior and put it in a fork of your github repository, it's available here: https://github.com/Seylox/appbar-snap-behavior The easiest way to use it is to copy ScrollingViewBehavior.java and AppBarSnapBehavior.java into your project and use them as layoutbehaviors in your xml. – Bernd Kampl Jun 24 '16 at 08:40
1

Hi Use the below layout it will work exactly like the google play store app i have tested it

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/appbar_padding_top"
    android:theme="@style/AppTheme.AppBarOverlay">

    <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|snap"
        app:popupTheme="@style/AppTheme.PopupOverlay">

    </android.support.v7.widget.Toolbar>

    <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/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

Also note i have used the following design support library

compile 'com.android.support:design:23.1.1'

let me know in case of any issue i will surely help you on that.

Sandy
  • 985
  • 5
  • 13
  • I am using this layout:http://hastebin.com/liluzinoxu.xml It's the same as yours except, an additional ViewFlipper at the end and a tabMode="scrollable"...but the recycler view is still scrolling with the app bar...should I try removing the viewFlipper?....changing the support design lib from 23.1.0 to 23.1.1 just made the animation of the snap faster and made no other difference – penduDev Nov 27 '15 at 07:28
1

i've managed to 'bypass' the problem.

I've created an abstract class, use it in your projects !

THIS IS LAYOUT:

<FrameLayout
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
        >

        <android.support.v4.view.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/status_bar_height" />

        <FrameLayout
            android:id="@+id/appBarLayout"
            android:layout_width="match_parent"
            android:layout_height="@dimen/status_bar_app_bar"
            android:background="@color/appbar"
            >

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:layout_marginTop="@dimen/status_bar_height"
                android:background="@color/appbar" />

            <android.support.design.widget.AppBarLayout 
                android:id="@+id/tabLayout"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_gravity="bottom"
                android:background="@color/appbar"
                />

        </FrameLayout>

    </FrameLayout>

The framelayout'll be your "new" appbar.

Then, your fragments (the ones in the viewpager) must extend this class:

public abstract class SnappableAppBarFragment extends Fragment {

    public int scrollAttuale;
    private boolean attivaSnap = true;
    private boolean isTouching = false;

    public void setSnapActivated(boolean state){attivaSnap = state;}

    public void setUpSnappableAppbar(final View fragMainView, final NestedScrollView nestedScrollView, final FrameLayout appBar, final int actionBarHeight) {
        nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (!attivaSnap)
                    return;
                int scrollY = nestedScrollView.getScrollY();
                int differenza = scrollAttuale - scrollY;
                if (differenza > 0 && appBar.getY() < 0) {
                    //Esci
                    appBar.animate().translationYBy(differenza).setDuration(0).start();
                    if (appBar.getY() > 0) {
                        appBar.animate().translationY(0).setDuration(0).start();
                    }
                }
                if (differenza < 0 && appBar.getY() > -actionBarHeight) {
                    //Entra
                    appBar.animate().translationYBy(differenza).setDuration(0).start();
                    if (appBar.getY() < -actionBarHeight)
                        appBar.animate().translationY(-actionBarHeight).setDuration(0).start();
                }

                if (differenza >= -2 && differenza <= 2 && !isTouching ){
                    int spazioTot = actionBarHeight;
                    if ((Math.abs(appBar.getY()) < spazioTot / 2 || nestedScrollView.getScrollY() <= 200) && appBar.getY() != 0) {
                        //Espandi
                        appBar.animate().translationY(0).setDuration(270).start();
                    } else if (appBar.getY() != 0) {
                        //Chiudi
                        appBar.animate().translationY(-actionBarHeight).setDuration(270).start();
                    }
                }
                scrollAttuale = scrollY;
                //Scrolling verso l'alto differenza è positiva
                //Scrolling verso il basso differenza è negativa
            }
        });
        fragMainView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (!attivaSnap)
                    return false;

                if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                    isTouching = false;
                    Handler handler = new Handler();
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            int spazioTot = actionBarHeight;
                            //   && nestedScrollView.getScrollY() <= 200  && nestedScrollView.getScrollY() <= 200   && nestedScrollView.getScrollY() <= 200
                            if ((Math.abs(appBar.getY()) < spazioTot / 2 || nestedScrollView.getScrollY() <= 200) && appBar.getY() != 0) {
                                //Espandi
                                appBar.animate().translationY(0).setDuration(270).start();
                            } else if (appBar.getY() != 0) {
                                //Chiudi
                                appBar.animate().translationY(-actionBarHeight).setDuration(270).start();
                            }
                        }
                    }, 0);
                }
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_SCROLL) {
                    isTouching = true;
                }
                return false;
            }
        });
    }
}

And finally, in fragments:

 public class Fragment1 extends SnappableAppBarFragment {

          ...

        @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                     Bundle savedInstanceState) {
                // Inflate the layout for this fragment
                viewPrincipale = inflater.inflate(R.layout.fragment_home, container, false);

                mainActivity = (MainActivity) getActivity();

                setUpSnappableAppbar(viewPrincipale, (NestedScrollView) viewPrincipale.findViewById(R.id.nestedScrollView), parentAppBar, actionBarHeight);

        ...

        }

        public void setParentAppBar(FrameLayout frameLayout) {
            parentAppBar = frameLayout;
        }

        public void setActionBarHeight(int height) {
            actionBarHeight = height;
        }   
        ...
    }

Eventually, when declaring fragments, set parentappbar and actionbarheight from activity:

fragment1.setParentAppBar((FrameLayout) findViewById(R.id.appBarLayout));
fragment1.setActionBarHeight(toolbar.getLayoutParams().height);

That's it, sorry if it's long but is the only way i found in order to make it work !

Also, sorry for bad english, i'm an italian developer :P Bye

EDIT: IMPORTANT!! CHANGE IN SNAPPABLEAPPBARFRAGMENT THIS: final int actionBarHeight TO final float actionBarHeight !!! IT'LL BE SMOOTHER :D

Narger
  • 11
  • 2