android - extended - floating action button menu
AnimaciĆ³n FAB con viewpager/tabslider (6)
Estoy tratando de encontrar la manera de lograr tal efecto https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B6Okdz75tqQsVlhxNGJCNTEzNFU/components-buttons-fab-behavior_04_xhdpi_009.webm
Estoy usando android.support.v4.view.ViewPager
Aprecio cualquier consejo y ayuda
Aquí hay otra solución. El problema para mí con otros es que otorgar el control de la FAB a la actividad que contiene el ViewPager hace que sea difícil codificarlo.
Necesito muchos controles en la FAB por parte de los Fragmentos para cambiar fácilmente el ícono de la FAB y establecer oyentes específicos en él.
A grandes rasgos, los pasos son:
- Declara un FAB en la actividad principal que contiene el ViewPager, y no en los fragmentos (es un paso obligatorio que quería evitar, pero la magia viene después; hacer lo contrario dificulta las cosas para una buena animación)
- En cada fragmento que se muestra en el ViewPager, creo un método donde puedo pasar un FAB, como
public void shareFab(FloatingActionButton sharedFab)
; Dentro de este método, cada fragmento establecerá el ícono FAB y agregará los oyentes necesarios. - Configuré una escucha
ViewPager.OnPageChangeListener
en el ViewPager que activará animaciones y llamará a mis métodosshareFab
.
Por lo tanto, cada vez que se muestra un fragmento, la actividad contenedora dará al fragmento mostrado la referencia de la FAB. El fragmento luego reinicia / recicla el FAB para satisfacer sus necesidades.
Aquí hay un ejemplo:
Los fragmentos a desplegar.
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
});
}
}
}
La actividad del contenedor
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;
}
}
});
}
}
... y el trazado clásico de la actividad principal.
<?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>
Los pros son:
- Animación como la descrita en las pautas de Diseño de Materiales!
- Control total de la FAB por cada fragmento.
Los contras son:
- ya que la FAB se puede compartir con otros fragmentos, la referencia de la FAB debe verificarse cada vez que se asegure que no sea nula
- Declaración de la FAB a nivel del ViewPager, un poco engorroso
La solución que descubrí fue:
Mis fragmentos que se muestran dentro de mi ViewPager implementan una interfaz (en mi caso
FloatingActionButtonFragment
). Esta interfaz requiere que el fragmento tenga un métodohandleFABClick(View)
.Mi oyente de cambio de ViewPager se ve a continuación:
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); } }); } }
Esto hace que la FAB se "esconda" cuando el usuario está cambiando las vistas, pero luego activa el método de mostrar cuando el estado de la vista está inactivo (se ha calmado) o se está resolviendo (necesitaba hacer las dos cosas porque no me sentía bien usando solo ocioso).
Para mi problema, no quería mostrar el botón de acción flotante en la primera pestaña; Para lograr esto agregué
android:visibility="invisible"
En barra de acción flotante.
<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" />
Y he agregado ViewpagerListner en mi actividad como lo sugiere @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) {
}
});
Para una buena animación de hide y show usa esto:
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;
}
}
});
Primero, debe tener el diseño con el botón de acción flotante anclado al 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>
Ahora que tiene el FAB anclado en el ViewPager (nota: lo he intentado en un fragmento que es una pestaña en el viewpager; parece que no se comporta correctamente), agregue un OnPageChangeListener a su ViewPager así:
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) {
}
});
Las animaciones para el "pop" y el "encogimiento" al cambiar de pestaña se manejan automáticamente con la nueva versión de la biblioteca de soporte.
Recientemente encontré una solución mucho mejor con la que estoy satisfecho. El método de ocultación FAB también puede tener un detector de parámetros que se activa cuando el botón está oculto (el método de mostrar también lo tiene). El único inconveniente es que tiene que implementar los cambios de viewpager y tablayout manualmente.
@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) {
}
Si desea mostrar FAB solo en pestañas específicas, puede cambiar el método onTabSelected para:
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab.getPosition() == 2) {
createButton.hide();
}
viewPager.setCurrentItem(tab.getPosition());
}