How to change FloatingActionButton between Tabs?

2019-03-07 22:32发布

I'm trying to implement FloatingActionButton from Google Design Support Library into two of three tabs, and according to the Material Design Guidelines - FloatingActionButton it says:

If there is a floating action button on multiple lateral screens (such as on tabs), upon entering each screen, the button should show and hide if the action contained on each is different. If the action is the same, the button should stay on screen (and translate to a new position, if necessary.)

Example of FAB animation in tabs

How can I make this kind of transition or animation for the FAB buttons in my app?

7条回答
疯言疯语
2楼-- · 2019-03-07 22:43

That's what worked for me:

private boolean isFloatingActionButtonHidden = false;
private int[] colorIntArray = {R.color.walking,R.color.running,R.color.biking,R.color.paddling,R.color.golfing};
private int[] iconIntArray = {R.drawable.ic_walk_white,R.drawable.ic_run_white,R.drawable.ic_bike_white,R.drawable.ic_add_white,R.drawable.ic_arrow_back_white};

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:
                // This is triggered just before the view pager reaches the final state
                // if you want to trigger the animation after the page reaches its final position
                // just move this to "case ViewPager.SCROLL_STATE_IDLE:"
                showFloatingActionButton(viewPager.getCurrentItem());
                break;
            case ViewPager.SCROLL_STATE_IDLE:
                // This is only triggered if user pulls to the left of the start or right of the end
                if (isFloatingActionButtonHidden) {
                    showFloatingActionButton(viewPager.getCurrentItem());
                }
                break;
            default:
                // in all other cases just hide the fab if it is not visable
                if (!isFloatingActionButtonHidden) {
                    hideFloatingActionButton();
                }
        }
   }
});

private void showFloatingActionButton(int position) {
    fab.setImageDrawable(getResources().getDrawable(iconIntArray[position], null));

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
        floatingActionButton.setBackgroundTintList(getResources().getColorStateList(iconIntArray[position], getTheme()));
    } else {
        floatingActionButton.setBackgroundTintList(getResources().getColorStateList(iconIntArray[position]));
    }

    floatingActionButton.show();
}

private void hideFloatingActionButton() {
    isFloatingActionButtonHidden = true;
    floatingActionButton.hide();
}
查看更多
一夜七次
3楼-- · 2019-03-07 22:45

Below is the simple way to achieve your desired result

enter image description here

Add two (or equivalent to your tab actions) FloatingActionButton in your main activity like below

<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"
        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" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fabChat"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="end|bottom"
    android:layout_margin="@dimen/fab_margin"
    android:src="@drawable/ic_fab_chat" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fabPerson"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="end|bottom"
    android:layout_margin="@dimen/fab_margin"
    android:src="@drawable/ic_fab_person"
    android:visibility="gone" />

Now in your MainActivity.java use Fab's default functions to hide and show on each tab selection like below

private void animateFab(int position) {
    switch (position) {
        case 0:
            fabChat.show();
            fabPerson.hide();
            break;
        case 1:
            fabPerson.show();
            fabChat.hide();
            break;

        default:
            fabChat.show();
            fabPerson.hide();
            break;
    }
}

Call animateFab function as below

TabLayout.OnTabSelectedListener onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        animateFab(tab.getPosition());
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

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

    }
};

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

    }

    @Override
    public void onPageSelected(int position) {
        animateFab(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
};
查看更多
相关推荐>>
4楼-- · 2019-03-07 22:54

Expanding upon blackcj's & kirtan403's answers, I have also added the ability to hide the fab on a chosen tab (in this case the 1st tab), which answer's bernzkie's question under blackcj's answer.

To achieve this I first declared int[]'s with 3 items, each for the fab of each of the 3 tabs. I set the first item in each to 0 because that will be the 1st tab's invisible fab that doesn't need resources.

int[] colorIntArray = {0, R.color.fab_red, R.color.fab_green};
int[] iconIntArray = {0, R.drawable.fab_pencil, R.drawable.fab_chevron};

I then set an if statement in the onCreate method of the Activity that features the fab and tabs. This statement hides the fab and scales it down, so that when it is made visible again, it can be made to only scale up, not down unnecessarily, then up again. I set the scale to match final scale of blackcj's scale down animation.

    if (tabLayout.getSelectedTabPosition() == 0) {
        // if on the 1st tab
        fab.hide();
        // scale down to only scale up when switching from 1st tab
        fab.setScaleX(0.2f);
        fab.setScaleY(0.2f);
    }

Then outside the onCreate method, I added blackcj's animateFab method, with kirtan403's rotate modification. However, I modified the animateFab method to also have a conditional statement, where:

  • if it returns to the 1st tab, the fab is hidden (it scales down automatically when hiding);

  • when switching from a tab where the fab is already full size and visible to another tab where it is meant to be visible, it performs the full scale down, change & scale up animation;

  • when switching from the tab with the hidden fab (in this case the 1st tab), the fab is made visible, then scaled up (not scaled down, then scaled up) with the custom animation.

    protected void animateFab(final int position) {
        fab.clearAnimation();
        if (tabLayout.getSelectedTabPosition() == 0) {
            // if on the 1st tab
            fab.hide();
        } else if (fab.getScaleX() == 1f) {
            // if the fab is full scale
            // Scale down animation
            ScaleAnimation shrink =  new ScaleAnimation(1f, 0.2f, 1f, 0.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            shrink.setDuration(150);     // animation duration in milliseconds
            shrink.setInterpolator(new DecelerateInterpolator());
            shrink.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animation animation) {
                    // Change FAB color and icon
                    fab.setBackgroundTintList(getResources().getColorStateList(colorIntArray[position]));
                    fab.setImageDrawable(getResources().getDrawable(iconIntArray[position], null));
    
                    // Scale up animation
                    ScaleAnimation expand = new ScaleAnimation(0.2f, 1f, 0.2f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    expand.setDuration(100);     // animation duration in milliseconds
                    expand.setInterpolator(new AccelerateInterpolator());
    
                    // Rotate Animation
                    Animation rotate = new RotateAnimation(60.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    rotate.setDuration(200);
                    rotate.setInterpolator(new DecelerateInterpolator());
    
                    // Add both animations to animation state
                    AnimationSet animationSet = new AnimationSet(false); //false means don't share interpolators
                    animationSet.addAnimation(expand);
                    animationSet.addAnimation(rotate);
                    fab.startAnimation(animationSet);
                }
    
                @Override
                public void onAnimationRepeat(Animation animation) {
    
                }
            });
            fab.startAnimation(shrink);
        } else {
            // if the fab is already scaled down
            // Change FAB color and icon
            fab.setBackgroundTintList(getResources().getColorStateList(colorIntArray[position]));
            fab.setImageDrawable(getResources().getDrawable(iconIntArray[position], null));
            fab.show();
    
            // Scale up animation
            ScaleAnimation expand = new ScaleAnimation(0.2f, 1f, 0.2f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            expand.setDuration(100);     // animation duration in milliseconds
            expand.setInterpolator(new AccelerateInterpolator());
    
            // Rotate Animation
            Animation rotate = new RotateAnimation(60.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rotate.setDuration(200);
            rotate.setInterpolator(new DecelerateInterpolator());
    
            // Add both animations to animation state
            AnimationSet animationSet = new AnimationSet(false); //false means don't share interpolators
            animationSet.addAnimation(expand);
            animationSet.addAnimation(rotate);
            fab.startAnimation(animationSet);
        }
    
    }
    

Then I just added blackcj's unchanged tab selection listener to the onCreate method.

    tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            viewPager.setCurrentItem(tab.getPosition());
            animateFab(tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {

        }

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

        }
    });

Hope this helps, it certainly works flawlessly for me. Thanks to blackcj & kirtan403. :)

查看更多
时光不老,我们不散
5楼-- · 2019-03-07 22:55

Finally, I've found a solution that's pretty easy and shows an animation that's exactly like the attached gif – in @Nauman's oder @Omid's solutions, the show animation starts before the hide animation has finished. But be sure to use the newest Support Library! I've tested it with version 23.2.1.

Use case:

  • Show fab 1 on tab 1 (index 0)
  • Show fab 2 on tab 2 (index 1)
  • Don't show any fab on tab 3 (index 2)

In your xml, place to fabs with unique id's and visibility set to invisible:

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    android:src="@drawable/some_icon"
    android:visibility="invisible" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    android:src="@drawable/another_icon"
    android:visibility="invisible" />

Then, add two fields for your fabs to your Activity (you can also use local variables or get the View each time by findViewById(...)):

public class MainActivity extends AppCompatActivity {
    private FloatingActionButton fab1;
    private FloatingActionButton fab2;

In your onCreate(...) function, find these views and save them into the declared fields:

fab1 = (FloatingActionButton) findViewById(R.id.fab1);
fab2 = (FloatingActionButton) findViewById(R.id.fab2);

Next declare a function that shows the right fab for the given position. The default case (tab 3 or more) is quite easy: Just call the hide() method on the fabs. show() and hide() already implement a scaling animation. But if we hide fab2 on tab 1, we have to wait until it has finished before we can show fab1. So set an FloatingActionButton.OnVisibilityChangedListener as parameter for the hide(...) method and show the desired new fab in the onHidden(...) method of that listener. The result is this:

public void showRightFab(int tab) {
    switch (tab) {
        case 0:
            fab2.hide(new FloatingActionButton.OnVisibilityChangedListener() {
                @Override
                public void onHidden(FloatingActionButton fab) {
                    fab1.show();
                }
            });
            break;

        case 1:
            fab1.hide(new FloatingActionButton.OnVisibilityChangedListener() [
                @Override
                public void onHidden(FloatingActionButton fab) {
                    fab2.show();
                }
            });
            break;

        default:
            fab1.hide();
            fab2.hide();
            break;
    }
}

That was the most difficult part! Now add a listener to the ViewPager to call the showRightFab(...) function each time the selected tab changes.

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        showRightFab(position);
    }

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

    @Override
    public void onPageScrollStateChanged(int state) {}
});

At last, call the function one time manually in the onCreate(...) method to show the fab at the default tab, because the ViewPager.OnPageChangeListener's onPageSelected(...) method isn't called on startup (e. g. otherwise if you open the app and it shows tab 1, no fab will be shown because the showRightFab(...) function has never been called).

showRightFab(viewPager.getCurrentItem());

That work's perfectly in my application!

查看更多
霸刀☆藐视天下
6楼-- · 2019-03-07 23:02

you can add a listener to viewpager and show and hide fab according to its state when you start scrolling the viewpager this is the order of states SCROLL_STATE_DRAGGING SCROLL_STATE_SETTLING SCROLL_STATE_IDLE

for example:

viewPager.addOnPageChangeListener(this);
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {

    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(state==ViewPager.SCROLL_STATE_IDLE)
            fab.show();
        else if(state==ViewPager.SCROLL_STATE_DRAGGING)
            fab.hide();

    }
查看更多
Viruses.
7楼-- · 2019-03-07 23:05

Extending blackcj's Answer, The solution worked really good as explained. However I would like to add something in that.

I have watched that video in slow motion. The drawable and fab are animating differently. When hiding, fab and drawable are in sync. While showing, fab is coming back first, and after 60-70 percent completion the drawable start animation from 0 and rotating and scaling coming to full size.

However, I was not able to achieve drawable animating saperatly. But, I managed to rotate and scale with different Interpolators and slightly modified time. So it seems more like in the video which is also presented in google design guidelines.

int[] colorIntArray = {R.color.red,R.color.gray,R.color.black};
int[] iconIntArray = {R.drawable.ic_btn1, R.drawable.ic_btn2, R.drawable.ic_btn3};

    protected void animateFab(final int position) {
        fab.clearAnimation();

        // Scale down animation
        ScaleAnimation shrink = new ScaleAnimation(1f, 0.1f, 1f, 0.1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        shrink.setDuration(100);     // animation duration in milliseconds
        shrink.setInterpolator(new AccelerateInterpolator());
        shrink.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // Change FAB color and icon
                fab.setBackgroundTintList(ContextCompat.getColorStateList(getApplicationContext(), colorIntArray[position]));
                fab.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), iconIntArray[position]));

                // Rotate Animation
                Animation rotate = new RotateAnimation(60.0f, 0.0f,
                        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                        0.5f);
                rotate.setDuration(150);
                rotate.setInterpolator(new DecelerateInterpolator());

                // Scale up animation
                ScaleAnimation expand = new ScaleAnimation(0.1f, 1f, 0.1f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                expand.setDuration(150);     // animation duration in milliseconds
                expand.setInterpolator(new DecelerateInterpolator());

                // Add both animations to animation state
                AnimationSet s = new AnimationSet(false); //false means don't share interpolators
                s.addAnimation(rotate);
                s.addAnimation(expand);
                fab.startAnimation(s);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        fab.startAnimation(shrink);
    }

And tab tab change listener as usual:

tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        mViewPager.setCurrentItem(tab.getPosition());
        animateFab(tab.getPosition());
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

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

    }
});
查看更多
登录 后发表回答