Как изменить FloatingActionButton между вкладками?

Я пытаюсь реализовать FloatingActionButton из библиотеки поддержки дизайна Google на двух из трех вкладок и в соответствии с Рекомендации по дизайну материалов - FloatingActionButton говорит:

Если есть плавающая кнопка действия на нескольких боковых экранах (например, на вкладках), при входе на каждый экран кнопка должна отображаться и скрываться, если действие, содержащееся на каждом из них, отличается. Если действие такое же, кнопка должна оставаться на экране (и при необходимости переместиться в новое положение).

Пример FAB-анимации во вкладках

Как я могу сделать такой переход или анимацию для кнопок FAB в моем приложении?


person Andres Valencia    schedule 14.07.2015    source источник
comment
У вас есть для этого XML-код?   -  person Andres Talavera    schedule 01.07.2016
comment
но все же ваше действие на fab.setOnclick..... по-прежнему такое же, как - для включения различных действий, поэтому вы меняете его в том же месте, сохраняете int position , обновляете его между tablayout.OnPageChnge ... , поэтому на вкладке 1 position = 1 и в вашем fab onclick `switch ( отравление): `   -  person user46772    schedule 29.07.2016


Ответы (7)


Эта функция в настоящее время не встроена в FloatingActionButton, поэтому вам придется анимировать ее самостоятельно. Предполагая, что ваш FloatingActionButton находится в вашем основном действии, добавьте к своему действию следующую функцию.

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

    }
});

Убедитесь, что у вас достаточно цветов и значков, чтобы соответствовать количеству имеющихся у вас вкладок.

person blackcj    schedule 14.07.2015
comment
Это сработало для меня. Но как я могу скрыть FAB для конкретной вкладки? скажем, на [position=2] я не хочу показывать сказку? - person bernzkie; 04.01.2016
comment
@bernzkie Я думаю, вы можете a if iconIntArray [position]! = 0 запускать все в animationEnd, чтобы анимация расширения никогда не вызывалась для достижения этого - person Louis Tsai; 24.04.2017

Ниже представлен простой способ добиться желаемого результата.

введите описание изображения здесь

Добавьте два (или эквивалент к действиям на вкладке) 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) {

    }
};
person Nauman Zubair    schedule 07.12.2015
comment
Это действительно очень просто, потому что show () и hide () уже реализуют анимацию. Но, к сожалению, показ () новых фабрик начинается до того, как завершается скрытие () старых фабрик. Можно ли вызвать show () после завершения анимации hide ()? Может со слушателем? - person Felix Edelmann; 17.03.2016
comment
Рад, что помог :) - person Nauman Zubair; 23.01.2017

Расширяя ответ blackcj, решение работало очень хорошо, как объяснялось. Однако я хотел бы кое-что добавить к этому.

Я смотрел это видео в замедленной съемке. Drawable и fab анимируются по-разному. При скрытии fab и drawable синхронизируются. Во время показа сначала возвращается fab, а после завершения на 60-70 процентов рисованная анимация запуска с 0, а также вращение и масштабирование достигают полного размера.

Однако мне не удалось добиться реалистичной анимации. Но мне удалось вращать и масштабировать с разными интерполяторами и немного измененным временем. Так что это больше похоже на видео, которое также представлено в гайдлайнах по дизайну.

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

    }
});
person kirtan403    schedule 11.12.2015

Расширяя ответы blackcj & kirtan403, я также добавил возможность скрывать fab на выбранной вкладке (в данном случае на 1-й вкладке), которая отвечает на вопрос bernzkie под ответом blackcj.

Чтобы добиться этого, я сначала объявил int[] с 3 элементами, каждый для fab каждой из 3 вкладок. Я установил для первого элемента в каждом значение 0, потому что это будет первая вкладка, невидимая fab, для которой не нужны ресурсы.

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

Затем я установил оператор if в onCreate методе Activity, который содержит 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, я добавил метод animateFab blackcj с модификацией rotate kirtan403. Однако я изменил метод animateFab, чтобы он также имел условный оператор, где:

  • если он возвращается на 1-ю вкладку, 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);
        }
    
    }
    

Затем я просто добавил прослушиватель неизмененного выбора вкладки blackcj в метод onCreate.

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

person inferKNOX    schedule 21.03.2016

Наконец, я нашел довольно простое решение, которое показывает анимацию, точно такую ​​же, как прилагаемый gif - в решениях @ Nauman oder @ Omid анимация показа начинается до завершения анимации скрытия. Но обязательно используйте новейшую библиотеку поддержки! Тестировал с версией 23.2.1.

Пример использования:

  • Показать фабу 1 на вкладке 1 (индекс 0)
  • Показать фаб 2 на вкладке 2 (индекс 1)
  • Не показывать фабулу на вкладке 3 (индекс 2)

В вашем xml поместите фабрики с уникальными идентификаторами и видимостью, установленными на невидимую:

<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 (вы также можете использовать локальные переменные или каждый раз получать View с помощью 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() уже реализуют анимацию масштабирования. Но если мы скроем fab2 на вкладке 1, нам придется дождаться его завершения, прежде чем мы сможем показать fab1. Итак, установите FloatingActionButton.OnVisibilityChangedListener в качестве параметра для метода hide(...) и покажите желаемую новую фабрику в методе onHidden(...) этого слушателя. Результат такой:

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(...), чтобы показать фабрику на вкладке по умолчанию, потому что метод ViewPager.OnPageChangeListener onPageSelected(...) не вызывается при запуске (например, в противном случае, если вы откроете приложение, и оно покажет вкладку 1, фабрика не будет отображаться, потому что функция showRightFab(...) никогда не вызывалась).

showRightFab(viewPager.getCurrentItem());

Эта работа отлично подходит для моего приложения!

person Felix Edelmann    schedule 17.03.2016

вы можете добавить слушателя в 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();

    }
person Omid Aminiva    schedule 09.11.2015
comment
Идея хорошая, но после смены вкладок анимация немного запаздывает. Я также пробовал с другим состоянием, но не повезло! - person kirtan403; 10.12.2015

Вот что сработало для меня:

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();
}
person Wilko    schedule 19.03.2016