web-dev-qa-db-fra.com

Android - FAB à masquer lors de la navigation entre différents fragments dans un viewpager

J'essaie de faire quelque chose de très simple.

Je souhaite que le FAB n'apparaisse que sur un onglet de mon TabLayout et soit masqué lors de la navigation vers un autre onglet. Ainsi, par exemple, un onglet vous permettrait d'ajouter de nouveaux éléments dans le FAB, mais l'onglet suivant ne vous permettrait pas d'ajouter des éléments.

J'ai suivi la disposition de conception XML "typique":

<?xml version="1.0" encoding="utf-8"?>
<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:orientation="vertical"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:id="@+id/container"
    xmlns:tools="http://schemas.Android.com/tools"
    >

<Android.support.design.widget.AppBarLayout
    Android:id="@+id/appBarLayout"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:minHeight="?attr/actionBarSize"
        Android:background="?attr/colorPrimary"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        app:layout_scrollFlags="scroll|enterAlways">

        <LinearLayout
            Android:id="@+id/search_container"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:gravity="center_vertical"
            Android:orientation="horizontal">

            <EditText
                Android:id="@+id/search_view"
                Android:layout_width="0dp"
                Android:layout_height="?attr/actionBarSize"
                Android:layout_weight="1"
                Android:background="@Android:color/transparent"
                Android:gravity="center_vertical"
                Android:hint="Search"
                Android:imeOptions="actionSearch"
                Android:inputType="text"
                Android:maxLines="1"
                Android:paddingLeft="2dp"
                Android:singleLine="true"
                Android:textColor="#ffffff"
                Android:textColorHint="#b3ffffff" />

            <ImageView
                Android:id="@+id/search_clear"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:layout_gravity="center"
                Android:paddingLeft="16dp"
                Android:paddingRight="16dp"
                Android:src="@drawable/ic_action_cancel" />

        </LinearLayout>

    </Android.support.v7.widget.Toolbar>

    <Android.support.design.widget.TabLayout
        Android:id="@+id/sliding_tabs"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="?attr/colorPrimary"
        app:tabMode="scrollable"
        />


</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="com.example.simon.behaviours.PatchedScrollingViewBehavior"/>

<Android.support.design.widget.FloatingActionButton
    Android:id="@+id/fab"
    app:borderWidth="0dp"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:src="@drawable/ic_action_new"
    Android:layout_margin="16dp"
    Android:layout_gravity="bottom|right"
    app:layout_anchor="@+id/viewPager"
    app:layout_anchorGravity="bottom|right|end"
    app:layout_behavior="com.example.simon.behaviours.ScrollingFABBehavior"
    Android:visibility="gone"
    app:fabSize="normal">
</Android.support.design.widget.FloatingActionButton>

</Android.support.design.widget.CoordinatorLayout>

J'ai utilisé le comportement suivant pour le FAB. Cela se traduit par des rouleaux ascendants entraînant la disparition du FAB et revient à l'écran sur un rouleau descendant:

public class ScrollingFABBehavior extends FloatingActionButton.Behavior {
    private int toolbarHeight;

    public ScrollingFABBehavior(Context context, AttributeSet attrs) {
        super();
        this.toolbarHeight = getToolbarHeight(context);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
        return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
        boolean returnValue = super.onDependentViewChanged(parent, fab, dependency);
        if (dependency instanceof AppBarLayout) {
            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
            int fabBottomMargin = lp.bottomMargin;
            int distanceToScroll = fab.getHeight() + fabBottomMargin;
            float ratio = (float)dependency.getY()/(float)toolbarHeight;
            fab.setTranslationY(-distanceToScroll * ratio);
        }
        return returnValue;
    }

    public static int getToolbarHeight(Context context) {
        final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
                new int[]{R.attr.actionBarSize});
        int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
        styledAttributes.recycle();

        return toolbarHeight;
    }
}

J'ai ajouté un viewpager addOnPageChangeListener:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        if (position == 0) {
            FloatingActionButton floatingActionButton = (FloatingActionButton) findViewById(R.id.fab);
            floatingActionButton.setVisibility(View.VISIBLE);
        }
        else
        {
            FloatingActionButton floatingActionButton = (FloatingActionButton) findViewById(R.id.fab);
            floatingActionButton.setVisibility(View.GONE);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

Je veux seulement que le FAB apparaisse sur la première page et disparaisse sur toutes les autres pages.

Le code fonctionne mais lorsque je glisse vers le bas sur la page suivante, le FAB apparaît après même si la visibilité est définie sur disparu. Je pense que cela a quelque chose à voir avec le comportement défini pour le FAB. Est-ce que quelqu'un sait pourquoi le FAB devient toujours visible sur un balayage vers le bas si la visibilité est définie sur disparu?

12
Simon

Cela n'a pas fini par être quelque chose que je veux implémenter dans mon application mais j'ai réussi à trouver une réponse à la fin, avec un peu d'aide en regardant comment ils ont implémenté la même chose sur le wordpress = app.

Dans l'application wordpress, nous voyons un bouton d'action flottant sur la première page de l'application qui disparaît si vous glissez sur l'une des autres pages du viseur:

wordpress

Ils l'ont fait via le code suivant - c'est le code de l'activité qui contient le viseur. Vous pouvez voir la partie pertinente du code sous la méthode onPageScrolled qui contient le bus d'événements qui publie un événement chaque fois que la page défile. L'événement ne contient qu'une seule variable appelée positionOffset qui est une valeur entière de 0 à 1. Si vous faites défiler et que la page est à mi-chemin entre les deux viewpagers, le positionOffset est de 0,5, vous obtenez l'idée:

WPMainActivity.Java

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                AppPrefs.setMainTabIndex(position);

                switch (position) {
                    case WPMainTabAdapter.TAB_NOTIFS:
                        new UpdateLastSeenTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                        break;
                }
                trackLastVisibleTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // noop
            }

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // fire event if the "My Site" page is being scrolled so the fragment can
                // animate its fab to match
                if (position == WPMainTabAdapter.TAB_MY_SITE) {
                    EventBus.getDefault().post(new MainViewPagerScrolled(positionOffset));
                }
            }
        });

L'événement est récupéré dans le fragment qui contient le code suivant. L'événement déclenchera la méthode translationY qui anime le FAB verticalement lorsque la page est défilée en fonction de la distance de défilement de la page, comme déterminé par la positionOffset:

MySiteFragment

/*
 * animate the fab as the users scrolls the "My Site" page in the main activity's ViewPager
 */
@SuppressWarnings("unused")
public void onEventMainThread(CoreEvents.MainViewPagerScrolled event) {
    mFabView.setTranslationY(mFabTargetYTranslation * event.mXOffset);
}

Enfin, la disposition dans le my_site_fragment.xml vous montre que le FAB est réellement placé dans les fragments xml au lieu de l'activité xml.

<!-- this coordinator is only here due to https://code.google.com/p/Android/issues/detail?id=175330 -->
<Android.support.design.widget.CoordinatorLayout
    Android:id="@+id/coordinator"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.design.widget.FloatingActionButton
        Android:id="@+id/fab_button"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_gravity="end|bottom"
        Android:layout_marginBottom="@dimen/fab_margin"
        Android:layout_marginRight="@dimen/fab_margin"
        Android:src="@drawable/gridicon_create_light"
        app:borderWidth="0dp"
        app:rippleColor="@color/fab_pressed" />
</Android.support.design.widget.CoordinatorLayout>
7
Simon

Hé, j'avais exactement le même problème que toi.

J'ai trouvé deux méthodes qui ont fonctionné pour moi.

Méthode 1:

Ajoutez un indicateur statique dans votre MainActivity: public static boolean fabVisible;.

J'ai remarqué que vous disposez d'un écouteur pour que votre viewPager détecte la page de fragment actuelle, vous pouvez donc ajouter ceci:

    public void onPageSelected(int position) {
        switch (position) {
            case 0:
                fabVisible = true;
                mFloatingActionButton.show();
                break;
            default:
                fabVisible = false;
                mFloatingActionButton.hide();
                break;
        }
    }

Et dans votre comportement fab personnalisé, ajoutez le code suivant:

    if (dyConsunmed > 0 && child.getVisibility() == View.VISIBLE ) {
            child.hide();
        } else if (dyConsunmed < 0 && child.getVisibility() != View.VISIBLE && MainActivity.fabVisible) {
            child.show();
        }

Le but de cette méthode est de s'assurer que le fab doit être visible avant d'implémenter la méthode hide & show.

Méthode 2:

Vous n'avez plus besoin d'ajouter d'indicateur comme "fabVisible" dans votre MainActivity. Bien que cette méthode vous oblige à définir le viewPager comme statique dans votre MainActivity, car nous devons pouvoir y accéder dans notre classe de comportement fab personnalisée.

Dans votre classe ScrollAwareFABBehavior, ajoutez le code suivant:

    if (MainActivity.viewPager.getCurrentItem() == 0) {
        if (dyConsunmed > 0 && child.getVisibility() == View.VISIBLE ) {
            child.hide();
        } else if (dyConsunmed < 0 && child.getVisibility() != View.VISIBLE) {
            child.show();
        }

    }

Le point clé de cette méthode, c'est uniquement de faire la méthode show et hide lorsque vous êtes à l'intérieur du fragment que vous voulez (dans mon cas c'est le fragment un donc le currenItem == 0). Bien sûr, pour supprimer le fab dans le deuxième fragment, vous devez toujours le cacher dans votre écouteur viewPager.

Si vous vous sentez toujours confus à propos de l'implémentation, n'hésitez pas à vérifier mon référentiel sur GitHub et à jouer avec le code. L'adresse est: https://github.com/Anthonyeef/FanfouDaily

Bien que le contenu du flux soit entièrement en chinois, le code est entièrement en anglais.

4
Anthonyeef

Implémentez ViewPager.onPageListener et remplacez onPageSelected et affichez ou masquez FAB sur les onglets selon vos besoins. Voici l'exemple de code:

@Override
public void onPageSelected(int position) {
     //FAB_PAGE is the index of the page on which you want to show FAB
    if(position == FAB_PAGE)
    {
        fab.setVisibility(View.VISIBLE);

    }
    else
    {
        fab.setVisibility(View.GONE);
    }

}

Vous pouvez également utiliser fab.show (); et fab.hide ();

Vérifiez également this pour plus de détails.

1
Juni

Créez un booléen statique dans votre activité (voir YourActivity.isFABinCurrentTabVisible Ci-dessous) et modifiez-le depuis votre ViewPager, en conséquence s'il doit y avoir un fab dans l'onglet actuel ou non. Cela pourrait être une mauvaise solution ... Et pensez à utiliser fab.show() et fab.hide() au lieu de setVisiblity(). Ceux-ci exécutent les animations de fondu d'entrée et de sortie.

public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {

public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
    super();
}

@Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                   final View directTargetChild, final View target, final int nestedScrollAxes) {
    // Ensure we react to vertical scrolling
    return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
            || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}

@Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                           final View target, final int dxConsumed, final int dyConsumed,
                           final int dxUnconsumed, final int dyUnconsumed) {
    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
        // User scrolled down and the FAB is currently visible -> hide the FAB
        child.hide();
    } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE && YourActivity.isFABinCurrentTabVisible) {
        // User scrolled up and the FAB is currently not visible -> show the FAB
        child.show();
    }
}
}
1
kphil

Vous pouvez le faire par programme lorsque vous passez à l'onglet suivant.

FloatingActionButton fab = (FloatingActionButton) view.findViewByID(R.id.fab)
fab.setVisibility(View.GONE);

Et puis définissez la visibilité sur View.VISIBLE lorsque vous glissez en arrière.

1
adao7000

On dirait que vous vous approchez de cela sous le mauvais angle ...

Ainsi, par exemple, un onglet vous permettrait d'ajouter de nouveaux éléments dans le FAB, mais l'onglet suivant ne vous permettrait pas d'ajouter des éléments.

Placez le FAB dans le Fragment de la première page au lieu de l'hébergement Fragment/Activity. De cette façon, il disparaîtra automatiquement avec le premier fragment de page - il s'animera même à côté. Il sera également à sa place, car il n'est pas du tout lié aux autres pages/onglets.

Si vous avez vraiment besoin de gérer l'événement click dans votre hébergement Fragment/Activity, rappelez votre premier fragment de page à l'hébergement Fragment/Activity lorsque ce clic se produit.

0
Nicklas