Великолепная анимация с viewpager/tabslider

Я пытаюсь найти способ добиться такого эффекта https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B6Okdz75tqQsVlhxNGJCNTEzNFU/components-buttons-fab-behavior_04_xhdpi_009.webm

Я использую android.support.v4.view.ViewPager

Я ценю любые подсказки и помощь


person ray20    schedule 23.07.2015    source источник


Ответы (6)


Во-первых, вам нужно, чтобы макет с плавающей кнопкой действия был привязан к 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>

Теперь, когда у вас есть FAB, привязанный к ViewPager (примечание: я пробовал это на фрагменте, который является вкладкой в ​​viewpager; похоже, он не работает должным образом), добавьте OnPageChangeListener в свой ViewPager следующим образом:

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

        }
    });

Анимации для «всплывания» и «сжатия» при переключении вкладок обрабатываются автоматически с новой версией библиотеки поддержки.

person Chantell Osejo    schedule 27.07.2015
comment
Спасибо, я уже достиг этого, но с пользовательской анимацией. У меня есть побочный вопрос, как я могу заставить FAB выполнять разные действия внутри фрагмента в зависимости от текущей страницы (каждая страница имеет свой собственный фрагмент) - person ray20; 28.07.2015
comment
@ ray20, я сейчас над этим работаю... свяжусь с вами, если что-нибудь придумаю. Из макушки можно было бы изменить действие при изменении страницы (там OnPageChangeListener поможет) на основе индекса фрагмента. Все еще неприятное решение, поскольку с тех пор вам также нужно изменить то, что делает щелчок, на основе выбранной в данный момент вкладки. Дам знать, если придумаю что-нибудь почище. - person Chantell Osejo; 30.07.2015
comment
Я сделал это, установив onClickListener в методе setUserVisibleHint. - person ray20; 31.07.2015
comment
Однако есть и другие проблемы - используя fab из библиотеки поддержки, я не могу заставить его автоматически анимироваться, как вы сказали. ViewPager и FloatingActionButton находятся в CoordinatorLayout, который находится в DrawerLayout, но не работает, я не знаю, должен ли я что-то делать в макете/коде, кроме установки атрибута привязки для FAB. Мне также интересно, как заставить Fab работать с viewPager и списками на вкладках. - person ray20; 31.07.2015
comment
Можете ли вы опубликовать свой код? Мой код также находится внутри макета ящика (хотя я опубликовал только часть макета координатора), и он работает (хотя и неприятным образом, который мне не слишком нравится), и я использую ViewPager с RecyclerViews на каждой вкладке. Однако вы должны привязать FAB к ViewPager, а не к списку внутри вкладки, особенно если вы сворачиваете панель инструментов. - person Chantell Osejo; 03.08.2015
comment
Кроме того, полное раскрытие: я тестировал это только на нескольких устройствах, и все они в основном устройства Nexus. - person Chantell Osejo; 03.08.2015
comment
Может ли кто-нибудь из вас помочь мне с моим вопросом относительно FAB и ViewPager? stackoverflow.com/ вопросы/32438819/ - person MatF; 30.09.2015
comment
Это просто здорово. Я не знал, что новая библиотека поддержки предоставляет эту функциональность из коробки. Это также помогло исправить ошибку, из-за которой FAB заикался во время прокрутки. - person Shubhral; 07.12.2016

Вот еще одно решение. Проблема для меня с другими заключается в том, что предоставление контроля над FAB активности, содержащей ViewPager, затрудняет кодирование.

Мне нужно много элементов управления на FAB с помощью Fragments, чтобы легко изменить значок FAB и установить на нем определенных слушателей.

Примерно шаги такие:

  1. Объявляет один FAB в основном действии, содержащем ViewPager, а не во фрагментах (это обязательный шаг, которого я хотел избежать, но волшебство приходит позже; в противном случае создание хорошей анимации усложняется)
  2. В каждом фрагменте, который будет отображаться в ViewPager, я создаю метод, в котором я могу передать FAB, например public void shareFab(FloatingActionButton sharedFab); внутри этого метода каждый фрагмент будет устанавливать значок FAB и добавлять необходимые слушатели
  3. Я настроил прослушиватель ViewPager.OnPageChangeListener на ViewPager, который будет запускать анимацию и вызывать мои методы shareFab.

Таким образом, каждый раз, когда отображается фрагмент, содержащая активность будет давать отображаемому фрагменту ссылку на FAB. Затем фрагмент сбрасывает/перезапускает FAB в соответствии со своими потребностями.

Вот пример:

Фрагменты для отображения

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

Действие контейнера

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

... и классическая схема основного действия

<?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>

Плюсы:

  • анимация, как описано в руководстве по дизайну материалов!
  • полный контроль над FAB каждым фрагментом

Минусы:

  • поскольку FAB может использоваться совместно с другими фрагментами, ссылка на FAB должна проверяться каждый раз, чтобы убедиться, что она не равна нулю
  • объявление FAB на уровне ViewPager, немного громоздко
person Vincent Hiribarren    schedule 19.04.2016

Для хорошей анимации скрытия и показа используйте это:

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

            }
        });
person NickUnuchek    schedule 21.06.2016

Решение, которое я обнаружил, было:

  1. Мои фрагменты, которые отображаются в моем ViewPager, реализуют интерфейс (в моем случае FloatingActionButtonFragment). Этот интерфейс требует, чтобы у фрагмента был метод handleFABClick(View).

  2. Мой прослушиватель изменений ViewPager выглядит следующим образом:

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

Это заставляет FAB «скрываться», когда пользователь меняет представления, но затем запускает метод show, когда состояние представления либо неактивно (оно установилось), либо устанавливается (мне нужно было сделать и то, и другое, поскольку это не казалось правильным, только используя праздный).

person Gavin Harris    schedule 03.02.2016

Недавно я нашел гораздо лучшее решение, которым я доволен. Метод скрытия FAB также может иметь параметр - слушатель, который срабатывает, когда кнопка скрыта (метод show также имеет это). Единственным недостатком является то, что вам придется вносить изменения в viewpager и tablayout вручную.

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

Если вы хотите показывать FAB только на определенных вкладках, вы можете изменить метод onTabSelected на:

@Override
public void onTabSelected(TabLayout.Tab tab) {
    if (tab.getPosition() == 2) {
        createButton.hide();
    }
    viewPager.setCurrentItem(tab.getPosition());
}
person Arūnas Bedžinskas    schedule 01.02.2016

Для моей проблемы я не хотел показывать плавающую кнопку действия на первой вкладке; для этого я добавил

android:visibility="invisible"

в плавающей панели действий.

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

И я добавил ViewpagerListner в свою деятельность, как это было предложено @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) {

            }
        });
person Zohra Khan    schedule 29.11.2016