如何在选项卡之间更改 FloatingActionButton?

How to change FloatingActionButton between Tabs?

我正在尝试实现 FloatingActionButtonGoogle 设计支持库 到三个选项卡中的两个,并根据 Material Design Guidelines - FloatingActionButton 它说:

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.)

如何为我的应用程序中的 FAB 按钮制作这种过渡或动画?

FloatingActionButton 目前未内置此功能,因此您必须自己为其设置动画。假设您的 FloatingActionButton 在您的主 activity 中,将以下函数添加到您的 activity.

int[] colorIntArray = {R.color.walking,R.color.running,R.color.biking,R.color.paddling,R.color.golfing};
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};

protected void animateFab(final int position) {
    fab.clearAnimation();
    // 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());
            fab.startAnimation(expand);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

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

更新颜色和可绘制资源以匹配您的项目。在 onCreate 方法中添加选项卡选择侦听器,并在选择选项卡时调用动画功能。

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) {

    }
});

确保您有足够的颜色和图标来匹配您拥有的标签数量。

你可以给viewpager添加一个监听器,根据状态显示和隐藏fab 当您开始滚动 viewpager 时,这是状态的顺序 SCROLL_STATE_DRAGGING SCROLL_STATE_SETTLING SCROLL_STATE_IDLE

例如:

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();

    }

以下是实现您想要的结果的简单方法

在您的主要 activity 中添加两个(或相当于您的选项卡操作)FloatingActionButton,如下所示

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

现在在您的 MainActivity.java 中使用 Fab 的默认功能来隐藏和显示每个选项卡选择,如下所示

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;
    }
}

调用animateFab函数如下

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) {

    }
};

扩展 blackcj 的答案,正如所解释的那样,该解决方案非常有效。但是我想补充一点。

我以慢动作观看了那个视频。 drawable 和 fab 的动画效果不同。隐藏时,fab 和 drawable 是同步的。显示时,fab 首先返回,完成 60-70% 后,drawable 从 0 开始动画,旋转和缩放变为全尺寸。

但是,我无法完全实现可绘制动画。但是,我设法使用不同的插值器和稍微修改的时间进行旋转和缩放。所以它看起来更像是 google 设计指南中也介绍的视频。

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);
    }

并且标签标签像往常一样改变监听器:

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) {

    }
});

最后,我找到了一个非常简单的解决方案,它显示的动画与附加的 gif 完全一样——在@Nauman 或@Omid 的解决方案中,显示动画在隐藏动画完成之前开始。但一定要使用最新的支持库!我已经用版本 23.2.1 测试了它。

用例:

  • 在选项卡 1(索引 0)上显示 fab 1
  • 在选项卡 2(索引 1)上显示 fab 2
  • 不在选项卡 3(索引 2)上显示任何 fab

在您的 xml 中,将唯一 ID 和可见性设置为不可见的晶圆厂放置:

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

然后,为你的晶圆厂添加两个字段到你的Activity(你也可以使用局部变量或每次通过findViewById(...)获取视图):

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

在您的 onCreate(...) 函数中,找到这些视图并将它们保存到声明的字段中:

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

接下来声明一个函数,显示给定位置的正确晶圆厂。默认情况(选项卡 3 或更多)非常简单:只需在晶圆厂上调用 hide() 方法。 show()hide() 已经实现了缩放动画。但是如果我们在选项卡 1 上隐藏 fab2,我们必须等到它完成才能显示 fab1。因此,将 FloatingActionButton.OnVisibilityChangedListener 设置为 hide(...) 方法的参数,并在该侦听器的 onHidden(...) 方法中显示所需的新 fab。结果是这样的:

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;
    }
}

那是最困难的部分!现在向 ViewPager 添加一个侦听器,以便在每次选定的选项卡更改时调用 showRightFab(...) 函数。

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) {}
});

最后,在onCreate(...)方法中手动调用一次函数,在默认选项卡显示fab,因为ViewPager.OnPageChangeListeneronPageSelected(...)方法没有被调用在启动时(例如,否则如果您打开应用程序并且它显示选项卡 1,则不会显示 fab,因为从未调用过 showRightFab(...) 函数)。

showRightFab(viewPager.getCurrentItem());

这项工作在我的申请中非常完美!

这对我有用:

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();
}

扩展 blackcj 和 kirtan403 的答案,我还添加了隐藏所选选项卡(在本例中为第一个选项卡)上的 fab 的功能,该选项卡在 blackcj 的答案下回答了 bernzkie 的问题。

为了实现这一点,我首先用 3 个项目声明了 int[],每个项目用于 3 个选项卡中每个选项卡的 fab。我将每个中的第一项设置为 0,因为这将是第一个选项卡的不可见 fab,不需要资源。

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

然后我在 ActivityonCreate 方法中设置一个 if 语句,该语句具有 fab 和制表符。此语句隐藏晶圆厂并将其按比例缩小,以便当它再次可见时,可以使其仅按比例放大,而不是不必要地缩小,然后再放大。我将比例设置为匹配 blackcj 缩小动画的最终比例。

    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);
    }

然后在onCreate方法外加了blackcj的animateFab方法,kirtan403的rotate修改。但是,我修改了 animateFab 方法,使其也有一个条件语句,其中:

  • 如果returns到第一个选项卡,fab隐藏(隐藏时自动缩小);

  • 当从 fab 已经全尺寸且可见的选项卡切换到另一个应该可见的选项卡时,它会执行完全缩小、更改和放大动画;

  • 当切换带有隐藏fab的选项卡(在本例中为第一个选项卡)时,fab可见,然后使用自定义动画放大(不是缩小,然后放大)。

    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);
        }
    
    }
    

然后我只是在onCreate方法中添加了blackcj的未更改的选项卡选择监听器。

    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) {

        }
    });

希望这对您有所帮助,它对我来说确实完美无缺。感谢 blackcj 和 kirtan403。 :)