FAB анимация с виджером / табулятором
Я пытаюсь найти способ добиться такого эффекта 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
Я ценю любые советы и помощь
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) {
}
});
Анимации для "всплывающих" и "сжатых" при переключении вкладок обрабатываются автоматически для вас с новой версией библиотеки поддержки.
Вот еще одно решение. Для меня проблема с другими заключается в том, что предоставление контроля над FAB для действия, которое содержит ViewPager, затрудняет кодирование.
Мне нужно много элементов управления FAB с помощью фрагментов, чтобы легко изменить значок FAB и установить для него определенных слушателей.
Примерно, шаги:
- Объявляет один FAB в основном действии, которое содержит ViewPager, а не во фрагментах (это обязательный шаг, которого я хотел избежать, но магия наступает после; выполнение в противном случае затрудняет хорошую анимацию)
- В каждом фрагменте, который будет отображаться в ViewPager, я создаю метод, в котором я могу передать FAB, например
public void shareFab(FloatingActionButton sharedFab)
; внутри этого метода каждый фрагмент будет устанавливать значок FAB и добавлять нужные слушатели - Я создал
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, немного громоздко
Для хорошей анимации прятки используйте это:
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;
}
}
});
Решение, которое я обнаружил, было:
Мои фрагменты, показанные в моем ViewPager, реализуют интерфейс (в моем случае
FloatingActionButtonFragment
). Этот интерфейс требует, чтобы фрагмент имел методhandleFABClick(View)
,Мой слушатель смены 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, когда состояние представления либо бездействует (оно установилось), либо устанавливается (мне нужно было сделать и то, и другое, так как это было неправильно при использовании в режиме ожидания).
Недавно я нашел гораздо лучшее решение, которое меня устраивает. Метод FAB hide также может иметь параметр-слушатель, который срабатывает, когда кнопка скрыта (метод 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());
}
Для моей проблемы я не хотел показывать плавающую кнопку действия на первой вкладке; чтобы добиться этого я добавил
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) {
}
});