Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 732
Simon Avatar asked Jul 16 '15 14:07

Simon


3 Answers

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>
like image 136
Simon Avatar answered Oct 27 '22 09:10

Simon


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.

like image 5
Anthonyeef Avatar answered Oct 27 '22 11:10

Anthonyeef


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.

like image 3
Junaid Avatar answered Oct 27 '22 10:10

Junaid