Android - FAB to hide when navigating between different fragments in a viewpager

Simon picture Simon · Jul 16, 2015 · Viewed 22.3k times · Source

I'm trying to do something really simple.

I would like the FAB to only appear on one tab in my TabLayout and be hidden when navigating to another tab. So for example, one tab would let you add new items in the FAB, but the next tab would not let you add items.

I have followed the 'typical' XML design layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    xmlns:tools="http://schemas.android.com/tools"
    >

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

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

        <LinearLayout
            android:id="@+id/search_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/search_view"
                android:layout_width="0dp"
                android:layout_height="?attr/actionBarSize"
                android:layout_weight="1"
                android:background="@android:color/transparent"
                android:gravity="center_vertical"
                android:hint="Search"
                android:imeOptions="actionSearch"
                android:inputType="text"
                android:maxLines="1"
                android:paddingLeft="2dp"
                android:singleLine="true"
                android:textColor="#ffffff"
                android:textColorHint="#b3ffffff" />

            <ImageView
                android:id="@+id/search_clear"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:src="@drawable/ic_action_cancel" />

        </LinearLayout>

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

    <android.support.design.widget.TabLayout
        android:id="@+id/sliding_tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        app:tabMode="scrollable"
        />


</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="com.example.simon.behaviours.PatchedScrollingViewBehavior"/>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    app:borderWidth="0dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_action_new"
    android:layout_margin="16dp"
    android:layout_gravity="bottom|right"
    app:layout_anchor="@+id/viewPager"
    app:layout_anchorGravity="bottom|right|end"
    app:layout_behavior="com.example.simon.behaviours.ScrollingFABBehavior"
    android:visibility="gone"
    app:fabSize="normal">
</android.support.design.widget.FloatingActionButton>

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

I have used the following behavior for the FAB. This results in any upscrolls to cause the FAB to disappear and will return back on screen on a downscroll:

public class ScrollingFABBehavior extends FloatingActionButton.Behavior {
    private int toolbarHeight;

    public ScrollingFABBehavior(Context context, AttributeSet attrs) {
        super();
        this.toolbarHeight = getToolbarHeight(context);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
        return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
        boolean returnValue = super.onDependentViewChanged(parent, fab, dependency);
        if (dependency instanceof AppBarLayout) {
            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
            int fabBottomMargin = lp.bottomMargin;
            int distanceToScroll = fab.getHeight() + fabBottomMargin;
            float ratio = (float)dependency.getY()/(float)toolbarHeight;
            fab.setTranslationY(-distanceToScroll * ratio);
        }
        return returnValue;
    }

    public static int getToolbarHeight(Context context) {
        final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
                new int[]{R.attr.actionBarSize});
        int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
        styledAttributes.recycle();

        return toolbarHeight;
    }
}

I have added a viewpager addOnPageChangeListener:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        if (position == 0) {
            FloatingActionButton floatingActionButton = (FloatingActionButton) findViewById(R.id.fab);
            floatingActionButton.setVisibility(View.VISIBLE);
        }
        else
        {
            FloatingActionButton floatingActionButton = (FloatingActionButton) findViewById(R.id.fab);
            floatingActionButton.setVisibility(View.GONE);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

I only want the FAB to appear on the first page and disappear on all the other pages.

The code works but when I swipe down on the next page, the FAB appears after even though the visibility is set to gone. I think it has something to do with the behavior set for the FAB. Does anyone know why the FAB still become visible on a swipe down if the visibility is set to gone?

Answer

Simon picture Simon · Nov 6, 2015

This didn't end up being something I want to implement in my app but I did manage to find an answer in the end, with some help by looking through how they implemented the same thing on the wordpress app.

In the wordpress app, we see a floating action button on the first page of the app which disappears if you swipe to any of the other pages on the viewpager:

wordpress

They did this through the following code - this is the code for the Activity that holds the viewpager. You can see the relevant part of the code under the onPageScrolled method which contains the eventbus that posts an event each time the page is scrolled. The event only contains one variable called positionOffset which is an integer value from 0 to 1. If you scroll and the page is half way between the two viewpagers, the positionOffset is 0.5, you get the idea:

WPMainActivity.java

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                AppPrefs.setMainTabIndex(position);

                switch (position) {
                    case WPMainTabAdapter.TAB_NOTIFS:
                        new UpdateLastSeenTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                        break;
                }
                trackLastVisibleTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // noop
            }

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // fire event if the "My Site" page is being scrolled so the fragment can
                // animate its fab to match
                if (position == WPMainTabAdapter.TAB_MY_SITE) {
                    EventBus.getDefault().post(new MainViewPagerScrolled(positionOffset));
                }
            }
        });

The event is picked up in the fragment which contains the following code. The event will fire off the translationY method which animates the FAB vertically when the page is scrolled according to how far the page is scrolled out of view as determined by the positionOffset:

MySiteFragment

/*
 * animate the fab as the users scrolls the "My Site" page in the main activity's ViewPager
 */
@SuppressWarnings("unused")
public void onEventMainThread(CoreEvents.MainViewPagerScrolled event) {
    mFabView.setTranslationY(mFabTargetYTranslation * event.mXOffset);
}

Finally, the layout in the my_site_fragment.xml shows you that the FAB is actually placed into the fragments xml instead of the activity xml.

<!-- this coordinator is only here due to https://code.google.com/p/android/issues/detail?id=175330 -->
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_marginBottom="@dimen/fab_margin"
        android:layout_marginRight="@dimen/fab_margin"
        android:src="@drawable/gridicon_create_light"
        app:borderWidth="0dp"
        app:rippleColor="@color/fab_pressed" />
</android.support.design.widget.CoordinatorLayout>