12

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?

Simon
  • 19,658
  • 27
  • 149
  • 217
  • 1
    `FAB` is just a regular view. You don't need to use special layout. Just change visibility or animate translation when the pager changes a fragment. – eleven Jul 16 '15 at 14:42
  • After looking at FAB's code, hide() set it's visibility to View.GONE, while show() set it's visibility to View.VISIBLE..so it looks like when we scrolled up the screen it called hide(), and when we scrolled down the screen it called show() which always set it back to View.VISIBLE.. – Dark Leonhart Sep 14 '15 at 08:32
  • Consider using standard `hide()` and `show()` `FAB` methods in `OnPageChangeListener` and make `FAB` visible on default. [This solution](http://stackoverflow.com/a/31663686/2047442) works for me. – irudyak Oct 12 '15 at 09:45

6 Answers6

8

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>
Simon
  • 19,658
  • 27
  • 149
  • 217
5

Hey I had exactly the same issue as you.

I found two methods which worked for me.

Method 1:

Add a static flag in your MainActivity: public static boolean fabVisible;.

I noticed you have a listener for your viewPager to detect the current fragment page, so you can add this:

    public void onPageSelected(int position) {
        switch (position) {
            case 0:
                fabVisible = true;
                mFloatingActionButton.show();
                break;
            default:
                fabVisible = false;
                mFloatingActionButton.hide();
                break;
        }
    }

And inside your custom fab behaviour, add the following code:

    if (dyConsunmed > 0 && child.getVisibility() == View.VISIBLE ) {
            child.hide();
        } else if (dyConsunmed < 0 && child.getVisibility() != View.VISIBLE && MainActivity.fabVisible) {
            child.show();
        }

The point of this method is to make sure the fab should be visible before you implement the hide & show method.

Method 2:

You don't need to add any flag like "fabVisible" in your MainActivity anymore. Although this method does require you to set the viewPager as static in your MainActivity, because we need to be able to access it in our custom fab behaviour class.

In your ScrollAwareFABBehavior class, add the following code:

    if (MainActivity.viewPager.getCurrentItem() == 0) {
        if (dyConsunmed > 0 && child.getVisibility() == View.VISIBLE ) {
            child.hide();
        } else if (dyConsunmed < 0 && child.getVisibility() != View.VISIBLE) {
            child.show();
        }

    }

The key point of this method, is only to make the show and hide method when you are inside the fragment you want (In my case it's fragment one so the currenItem == 0). Of course in order to remove the fab in the second fragment, you still need to hide it within your viewPager listener.

If you still feel confused about the implement, feel free to check my repository on GitHub and play with the code. The address is: https://github.com/Anthonyeef/FanfouDaily

Though the feed content is all in Chinese, the code is all in English.

Jack
  • 2,891
  • 11
  • 48
  • 65
Anthonyeef
  • 2,595
  • 1
  • 27
  • 25
3

Implement ViewPager.onPageListener and over ride onPageSelected and show or hide FAB on Tabs as per your requirements following is the sample code:

@Override
public void onPageSelected(int position) {
     //FAB_PAGE is the index of the page on which you want to show FAB
    if(position == FAB_PAGE)
    {
        fab.setVisibility(View.VISIBLE);

    }
    else
    {
        fab.setVisibility(View.GONE);
    }

}

You can also use fab.show(); and fab.hide();

Also check this for further details.

Community
  • 1
  • 1
Junaid
  • 4,822
  • 2
  • 21
  • 27
  • In lieu of using an `OnPageChangeListener` as you do above, you can use Rx and the RxBinding for a `ViewPager`. It'd look something like:`RxViewPager.pageSelections(mViewPager).subscribe([...])`. Subscribe to get the current page position. The nice thing about this is that you don't have to implement any of the other callbacks as you would with `OnPageChangeListener`. Just don't forget to unsubscribe. – Michael De Soto Aug 26 '16 at 22:13
1

You can do it programmatically when you swipe to the next tab.

FloatingActionButton fab = (FloatingActionButton) view.findViewByID(R.id.fab)
fab.setVisibility(View.GONE);

And then set the visiblity to View.VISIBLE when you swipe back.

adao7000
  • 3,632
  • 2
  • 28
  • 37
  • Hello, I tried your answer, but i wasn't successful. My app has a toolbar and FAB that disappears on swipe up and returns when swipe down. The FAB does disappear when I programmatically code it as you suggested in the addOnPageChangeListener but when I swipe down, the FAB 'returns' to view and becomes visible even though I set it to be invisible / gone. – Simon Jul 17 '15 at 15:58
1

Create a static boolean in your activity (see YourActivity.isFABinCurrentTabVisible below) and alter it from your ViewPager, accordingly if there should be a fab in the current tab or not. Might be a dirty fix though... And consider using fab.show() and fab.hide() instead of setVisiblity(). These perform the fade-in and fade-out animations.

public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {

public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
    super();
}

@Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                   final View directTargetChild, final View target, final int nestedScrollAxes) {
    // Ensure we react to vertical scrolling
    return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
            || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}

@Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                           final View target, final int dxConsumed, final int dyConsumed,
                           final int dxUnconsumed, final int dyUnconsumed) {
    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
        // User scrolled down and the FAB is currently visible -> hide the FAB
        child.hide();
    } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE && YourActivity.isFABinCurrentTabVisible) {
        // User scrolled up and the FAB is currently not visible -> show the FAB
        child.show();
    }
}
}
kphil
  • 891
  • 1
  • 9
  • 14
-1

It sounds like you're approaching this from the wrong angle...

So for example, one tab would let you add new items in the FAB, but the next tab would not let you add items.

Put the FAB in the Fragment of the first page instead of the hosting Fragment / Activity. That way it will automatically disappear along with the first page fragment - it will even animate alongside it. It will also be where it belongs, since it's not at all related to the other pages / tabs.

If you really need to handle the click event in your hosting Fragment / Activity, make your first page fragment call back to the hosting Fragment / Activity when that click occurs.

Nicklas Jensen
  • 1,424
  • 12
  • 19
  • Hello. I tried your approach but still wasn't able to solve it. The FAB appears to no longer be associated with the toolbar movements so if the toolbar disappears on swipe down, the FAB actually appears instead of disappears. If the toolbar is fully visible, the FAB is only half visible. I think the problem is inside the code for ScrollingFABBehavior. – Simon Jul 18 '15 at 08:53
  • 2
    If you put the FAB in each fragment, it will move in the viewpager, which is against the material design guidelines: https://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-behavior – android developer Aug 02 '15 at 11:11