FAB animation with viewpager/tabslider

2019-01-08 09:17发布

6条回答
啃猪蹄的小仙女
2楼-- · 2019-01-08 09:56

Here is another solution. The problem for me with other ones is that giving the control of the FAB to the activity that contains the ViewPager makes thing difficult to code.

I need lots of controls on the FAB by the Fragments to easily change the FAB icon and set specific listeners on it.

Roughly, the steps are:

  1. Declares one FAB in the main activity that contains the ViewPager, and not in the fragments (it is a mandatory step that I wanted to avoid, but the magic comes after; doing otherwise makes things difficult for a good animation)
  2. In each fragment to be displayed in the ViewPager, I create a method where I can pass a FAB, like public void shareFab(FloatingActionButton sharedFab); inside this method, each fragment will set the FAB icon and add the needed listeners
  3. I set up a ViewPager.OnPageChangeListener listener on the ViewPager that will trigger animations and calls my shareFab methods.

So each time a fragment is displayed, the containing activity will give to the displayed fragment the reference of the FAB. The fragment then reset/recycle the FAB to suit its needs.

Here is an example:

The fragments to display

public void Fragment1 extends Fragment {

    private FloatingActionButton mSharedFab;

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSharedFab = null; // To avoid keeping/leaking the reference of the FAB
    }

    public void shareFab(FloatingActionButton fab) {
        if (fab == null) { // When the FAB is shared to another Fragment
            if (mSharedFab != null) {
                mSharedFab.setOnClickListener(null);
            }
            mSharedFab = null;
        }
        else {
            mSharedFab = fab;
            mSharedFab.setImageResource(R.drawable.ic_search);
            mSharedFab.setOnClickListener((fabView) -> {
                // Your code
            });
        }
    }
}

The container activity

public class ActivityMain extends AppCompatActivity {

    FloatingActionButton mSharedFab;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mSharedFab = (FloatingActionButton) findViewById(R.id.shared_fab);

        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        Fragment1 fragment1 = new Fragment1();
        Fragment2 fragment2 = new Fragment2();
        adapter.addFragment(fragment1, "Fragment1");
        adapter.addFragment(fragment2, "Fragment2");
        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
        viewPager.setAdapter(adapter);

        fragment1.shareFab(mSharedFab); // To init the FAB
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
            @Override
            public void onPageSelected(int position) {  }
            @Override
            public void onPageScrollStateChanged(int state) {
                switch (state) {
                    case ViewPager.SCROLL_STATE_DRAGGING:
                        mSharedFab.hide(); // Hide animation
                        break;
                    case ViewPager.SCROLL_STATE_IDLE:
                        switch (viewPager.getCurrentItem()) {
                            case 0:
                                fragment2.shareFab(null); // Remove FAB from fragment
                                fragment1.shareFab(mSharedFab); // Share FAB to new displayed fragment
                                break;
                            case 1:
                            default:
                                fragment1.shareFab(null); // Remove FAB from fragment
                                fragment2.shareFab(mSharedFab); // Share FAB to new displayed fragment
                                break;
                        }
                        mSharedFab.show(); // Show animation
                        break;
                }
            }
        });
    }
}

... and the classical layout of the main activity

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

    </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/shared_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin" />

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

The pros are:

  • animation like the one described in the Material Design guidelines!
  • full control of the FAB by each fragments

The cons are:

  • since the FAB can be shared to another fragments, the reference of the FAB should be checked each time to be sure it is not null
  • declaration of the FAB at the level of the ViewPager, a little cumbersome
查看更多
乱世女痞
3楼-- · 2019-01-08 10:02

The solution that I discovered was:

  1. My fragments that are shown within my ViewPager implement an Interface (in my case FloatingActionButtonFragment). This interface requires that the fragment have a method handleFABClick(View).

  2. My ViewPager change listener then looks like below:

    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    
        }
    
        @Override
        public void onPageSelected(int position) {
    
    
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
    
            switch (state) {
                case ViewPager.SCROLL_STATE_SETTLING:
                    displayFloatingActionButtonIfNeeded(viewPager.getCurrentItem());
                    break;
    
                case ViewPager.SCROLL_STATE_IDLE:
                    displayFloatingActionButtonIfNeeded(viewPager.getCurrentItem());
                    break;
    
                default:
                    mFloatingActionButton.hide();
            }
    
        }
    });
    
    private void displayFloatingActionButtonIfNeeded(int position) {
        if (mFragmentStatePagerAdapter.getItem(position) instanceof FloatingActionButtonFragment) {
    
            final FloatingActionButtonFragment floatingActionButtonFragment = (FloatingActionButtonFragment) mFragmentStatePagerAdapter.getItem(position);
    
            mFloatingActionButton.show();
            mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    floatingActionButtonFragment.handleFABClick(v);
                }
            });
        }
    }
    

This makes the FAB 'hide' when the user is changing views, but then triggers the show method when the view state is either idle (it has settled) or is settling (I needed to do both as it didn't feel right only using idle).

查看更多
Deceive 欺骗
4楼-- · 2019-01-08 10:11

For my problem, I did not want to show floating action button on first Tab; in order to achieve this I added

android:visibility="invisible"

in floating action bar.

<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:visibility="invisible"
        app:srcCompat="@android:drawable/ic_input_add" />

And I have added ViewpagerListner in my activity as suggested by @chantell

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

            }

            @Override
            public void onPageSelected(int position) {

                switch (position) {
                    case 0:
                        addFloatingActionBt.hide();
                        break;
                    default:
                        addFloatingActionBt.show();
                        break;

                }

            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
查看更多
贼婆χ
5楼-- · 2019-01-08 10:15

I recently found much better solution that i am satisfied with. FAB hide method can also have a parameter - listener which is triggered when the button is hidden (show method also have this). The only drawback is that you have to implement viewpager and tablayout changes manually.

@Override
public void onPageSelected(int position) {
    onToolbarTitleChanged(adapter.getPageTitle(position).toString());
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}

@Override
public void onPageScrollStateChanged(int state) {
}

@Override
public void onTabSelected(TabLayout.Tab tab) {
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(final TabLayout.Tab tab) {
    fab.hide(new FloatingActionButton.OnVisibilityChangedListener() { // Hide FAB
        @Override
        public void onHidden(FloatingActionButton fab) {
            fab.show(); // After FAB is hidden show it again
        }
    });
}

@Override
public void onTabReselected(TabLayout.Tab tab) {
}

If you want to show FAB only on specific tabs, you can change the onTabSelected method to:

@Override
public void onTabSelected(TabLayout.Tab tab) {
    if (tab.getPosition() == 2) {
        createButton.hide();
    }
    viewPager.setCurrentItem(tab.getPosition());
}
查看更多
姐就是有狂的资本
6楼-- · 2019-01-08 10:17

For good animation of hide and show use this :

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

            }

            @Override
            public void onPageSelected(int position) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {
                switch (state){
                    case ViewPager.SCROLL_STATE_IDLE:
                        fab.show();
                        break;
                    case ViewPager.SCROLL_STATE_DRAGGING:
                    case ViewPager.SCROLL_STATE_SETTLING:
                        fab.hide();
                        break;
                }

            }
        });
查看更多
贪生不怕死
7楼-- · 2019-01-08 10:20

First, you need to have the layout with the floating action button anchored to the ViewPager:

<android.support.design.widget.CoordinatorLayout
    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/action_bar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            app:tabGravity="center"
            app:tabIndicatorColor="?attr/colorAccent"
            app:tabMode="scrollable"
            app:tabSelectedTextColor="?attr/colorAccent"
            app:tabTextColor="@android:color/black" />

    </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"
            app:layout_anchor="@id/viewpager"
            app:layout_anchorGravity="bottom|right|end"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:src="@drawable/ic_add_white"
            app:borderWidth="@dimen/fab_border_width"
            app:fabSize="normal" />

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

Now that you have the FAB anchored to the ViewPager (note: I've tried this on a fragment that is a tab in the viewpager; it does not seem to behave properly), add an OnPageChangeListener to your ViewPager like so:

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

        @Override
        public void onPageSelected(int position) {

            switch (position) {
                case INDEX_OF_TAB_WITH_FAB:
                    fab.show();
                    break;

                default:
                    fab.hide();
                    break;
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });

The animations for the "pop" and the "shrink" upon switching tabs are handled automatically for you with the new version of the Support Library.

查看更多
登录 后发表回答