web-dev-qa-db-fra.com

Android - le pied de page défile en dehors de l'écran lorsqu'il est utilisé dans CoordinatorLayout

J'ai une AppBarLayout qui défile hors de l'écran lorsque je fais défiler une RecyclerView. Sous la RecyclerView se trouve une RelativeLayout qui est un pied de page.

Le pied de page est affiché uniquement après un défilement vers le haut - il se comporte comme il le fait 

layout_scrollFlags="scroll|enterAlways"

mais il n'y a pas de drapeaux de défilement - s'agit-il d'un bogue ou est-ce que je fais quelque chose de mal? Je veux qu'il soit toujours visible

avant de défiler

enter image description here

après le défilement

enter image description here

Mettre à jour

ouvrit un google issue à ce sujet - il était marqué 'WorkingAsIntended', cela ne m'aide toujours pas car je veux une solution fonctionnelle d'un pied de page dans un fragment.

Mise à jour 2

vous pouvez trouver l'activité et le fragment xmls ici

notez que si la ligne 34 dans activity.xml - la ligne contenant app:layout_behavior="@string/appbar_scrolling_view_behavior" est commentée, le texte end est visible depuis le début - sinon, il est visible uniquement après un défilement vers le haut. 

55
Noa Drach

J'utilise une version simplifiée de la solution Learn OpenGL ES ( https://stackoverflow.com/a/33396965/778951 ), qui améliore la solution de Noa ( https://stackoverflow.com/a/31140112/1317564 ). Cela fonctionne bien pour ma simple barre d'outils à retour rapide au-dessus d'un TabLayout avec des boutons de pied de page dans le contenu ViewPager de chaque onglet.

Définissez simplement FixScrollingFooterBehavior en tant que layout_behavior sur le groupe View/ViewGroup que vous souhaitez conserver aligné en bas de l'écran.

Disposition:

<?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.support.design.widget.AppBarLayout
        Android:id="@+id/appbar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

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

            <Android.support.design.widget.TabLayout
                Android:id="@+id/tabs"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                app:tabMode="fixed"/>

    </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.spreeza.shop.ui.widgets.FixScrollingFooterBehavior"
        />

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

Comportement:

public class FixScrollingFooterBehavior extends AppBarLayout.ScrollingViewBehavior {

    private AppBarLayout appBarLayout;

    public FixScrollingFooterBehavior() {
        super();
    }

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

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {

        if (appBarLayout == null) {
            appBarLayout = (AppBarLayout) dependency;
        }

        final boolean result = super.onDependentViewChanged(parent, child, dependency);
        final int bottomPadding = calculateBottomPadding(appBarLayout);
        final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
        if (paddingChanged) {
            child.setPadding(
                child.getPaddingLeft(),
                child.getPaddingTop(),
                child.getPaddingRight(),
                bottomPadding);
            child.requestLayout();
        }
        return paddingChanged || result;
    }


    // Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
    private int calculateBottomPadding(AppBarLayout dependency) {
        final int totalScrollRange = dependency.getTotalScrollRange();
        return totalScrollRange + dependency.getTop();
    }
}
42
jhavatar

Mettre à jour

La solution ci-dessous ne fonctionne pas pour 5.1 car elle fonctionne dans 5 - au lieu de getTop use getTranslationY dans les calculs que vous effectuez.

layout.getTop()-->(int)layout.getTranslationY()
appbar.getTop()+toolbar.getHeight()-->(int)(appbar.getTranslationY()+toolbar.getHeight())

Mise à jour 2 Avec la nouvelle bibliothèque de support - 22.2.1 - Il n'y a pas de différence entre les versions 5.1 et précédente, vous devez uniquement utiliser getTop et ignorer la mise à jour précédente dans cette réponse.

Solution originale Après avoir regardé dans plusieurs directions, la solution est en réalité simple: ajoutez paddingBottom au fragment et ajustez-le au défilement de la page.

Le remplissage est nécessaire pour tenir compte des modifications de la barre d’outils y position - la mise en page du coordinateur déplace la page entière vers le haut et le bas à mesure que la barre d’outils disparaît et reparaît.

Pour ce faire, vous pouvez étendre AppBarLayout.ScrollingViewBehavior et définir le comportement de l'élément fragment de activity.

Voici les bases du code - cela fonctionne pour une activité avec seulement une barre d'outils - vous pouvez le remplacer par appbar.getTop() + toolbar.getHeight() et cela fonctionnera mieux si votre appbar inclut tabs.

activity.xml

<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/main"
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools"
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:elevation="3dp"
    app:elevation="3dp">
    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        app:layout_scrollFlags="scroll|enterAlways"
        />
</Android.support.design.widget.AppBarLayout>
<fragment
    Android:id="@+id/fragment"
    Android:name="com.example.noa.footer2.MainActivityFragment"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    app:layout_behavior="com.example.noa.footer2.MyBehavior"
    tools:layout="@layout/fragment"/>
</Android.support.design.widget.CoordinatorLayout>

fragment.xml

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
            xmlns:tools="http://schemas.Android.com/tools"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:paddingBottom="48dp"
            Android:background="@Android:color/holo_green_dark"
            tools:context=".MainActivityFragment">
<Android.support.v7.widget.RecyclerView
    Android:id="@+id/list"
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@null"/>
<View
    Android:layout_width="match_parent"
    Android:layout_height="100dp"
    Android:layout_alignParentBottom="true"
    Android:background="@Android:color/holo_red_light"/>
</RelativeLayout>

MainActivityFragment # onActivityCreated

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        CoordinatorLayout.LayoutParams lp = (LayoutParams) getView().getLayoutParams();
        MyBehavior behavior = (MyBehavior) lp.getBehavior();
        behavior.setLayout(getView());
    }

MyBehavior

public class MyBehavior extends AppBarLayout.ScrollingViewBehavior {

    private View layout;

    public MyBehavior() {
    }

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

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        boolean result = super.onDependentViewChanged(parent, child, dependency);
        if (layout != null) {
            layout.setPadding(layout.getPaddingLeft(), layout.getPaddingTop(), layout
                .getPaddingRight(), layout.getTop());
        }
        return result;
    }

    public void setLayout(View layout) {
        this.layout = layout;
    }
}
19
Noa Drach

J'ai commencé avec la solution de Noa ( https://stackoverflow.com/a/31140112/1317564 ) et cela a fonctionné pour le dragage au doigt, mais j'avais des problèmes avec les lancers. Après avoir passé du temps à rechercher les méthodes et à essayer différentes idées, voici la solution que j'ai trouvée:

// Workaround for https://code.google.com/p/Android/issues/detail?id=177195
// Based off of solution originally found here: https://stackoverflow.com/a/31140112/1317564
@SuppressWarnings("unused")
public class CustomScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
    private AppBarLayout appBarLayout;
    private boolean onAnimationRunnablePosted = false;

    @SuppressWarnings("unused")
    public CustomScrollingViewBehavior() {

    }

    @SuppressWarnings("unused")
    public CustomScrollingViewBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        if (appBarLayout != null) {
            // We need to check from when a scroll is started, as we may not have had the chance to update the layout at
            // the start of a scroll or fling event.
            startAnimationRunnable(child, appBarLayout);
        }
        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, final View child, int parentWidthMeasureSpec, int widthUsed,
                                  int parentHeightMeasureSpec, int heightUsed) {
        if (appBarLayout != null) {
            final int bottomPadding = calculateBottomPadding(appBarLayout);
            if (bottomPadding != child.getPaddingBottom()) {
                // We need to update the padding in onMeasureChild as otherwise we won't have the correct padding in
                // place when the view is flung, and the changes done in onDependentViewChanged will only take effect on
                // the next animation frame, which means it will be out of sync with the new scroll offset. This is only
                // needed when the view is flung -- when dragged with a finger, things work fine with just
                // implementing onDependentViewChanged().
                child.setPadding(child.getPaddingLeft(), child.getPaddingTop(), child.getPaddingRight(), bottomPadding);
            }
        }

        return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, final View child, final View dependency) {
        if (appBarLayout == null)
            appBarLayout = (AppBarLayout) dependency;

        final boolean result = super.onDependentViewChanged(parent, child, dependency);
        final int bottomPadding = calculateBottomPadding(appBarLayout);
        final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
        if (paddingChanged) {
            // If we've changed the padding, then update the child and make sure a layout is requested.
            child.setPadding(child.getPaddingLeft(),
                    child.getPaddingTop(),
                    child.getPaddingRight(),
                    bottomPadding);
            child.requestLayout();
        }

        // Even if we didn't change the padding, if onDependentViewChanged was called then that means that the app bar
        // layout was changed or was flung. In that case, we want to check for these changes over the next few animation
        // frames so that we can ensure that we capture all the changes and update the view pager padding to match.
        startAnimationRunnable(child, dependency);
        return paddingChanged || result;
    }

    // Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
    private int calculateBottomPadding(AppBarLayout dependency) {
        final int totalScrollRange = dependency.getTotalScrollRange();
        return totalScrollRange + dependency.getTop();
    }

    private void startAnimationRunnable(final View child, final View dependency) {
        if (onAnimationRunnablePosted)
            return;

        final int onPostChildTop = child.getTop();
        final int onPostDependencyTop = dependency.getTop();
        onAnimationRunnablePosted = true;
        // Start looking for changes at the beginning of each animation frame. If there are any changes, we have to
        // ensure that layout is run again so that we can update the padding to take the changes into account.
        child.postOnAnimation(new Runnable() {
            private static final int MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES = 5;
            private int previousChildTop = onPostChildTop;
            private int previousDependencyTop = onPostDependencyTop;
            private int countOfFramesWithNoChanges;

            @Override
            public void run() {
                // Make sure we request a layout at the beginning of each animation frame, until we notice a few
                // frames where nothing changed.
                final int currentChildTop = child.getTop();
                final int currentDependencyTop = dependency.getTop();
                boolean hasChanged = false;

                if (currentChildTop != previousChildTop) {
                    previousChildTop = currentChildTop;
                    hasChanged = true;
                    countOfFramesWithNoChanges = 0;
                }
                if (currentDependencyTop != previousDependencyTop) {
                    previousDependencyTop = currentDependencyTop;
                    hasChanged = true;
                    countOfFramesWithNoChanges = 0;
                }
                if (!hasChanged) {
                    countOfFramesWithNoChanges++;
                }
                if (countOfFramesWithNoChanges <= MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES) {
                    // We can still look for changes on subsequent frames.
                    child.requestLayout();
                    child.postOnAnimation(this);
                } else {
                    // We've encountered enough frames with no changes. Do a final layout request, and don't repost.
                    child.requestLayout();
                    onAnimationRunnablePosted = false;
                }
            }
        });
    }
}

Je ne suis pas partisan de revérifier la mise en page sur chaque image d'animation et cette solution n'est pas parfaite, car certains problèmes se sont produits lors de l'agrandissement/la réduction par programme de la présentation de la barre d'applications, mais je n'ai pas trouvé de meilleure solution . Les performances sont correctes sur un nouveau périphérique et acceptables sur un périphérique plus ancien. Si quelqu'un d'autre le fait, n'hésitez pas à prendre ma réponse comme source et à la republier.

17
Learn OpenGL ES
package pl.mkaras.utils;

import Android.content.Context;
import Android.support.design.widget.AppBarLayout;
import Android.support.design.widget.CoordinatorLayout;
import Android.support.v4.view.ViewCompat;
import Android.support.v7.widget.Toolbar;
import Android.util.AttributeSet;
import Android.view.View;
import Android.view.ViewGroup;
import Java.util.List;

public class ScrollViewBehaviorFix extends AppBarLayout.ScrollingViewBehavior {

    public ScrollViewBehaviorFix() {
        super();
    }

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

    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                                  int heightUsed) {
        if (child.getLayoutParams().height == -1) {
            List<View> dependencies = parent.getDependencies(child);
            if (dependencies.isEmpty()) {
                return false;
            }

            final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
            if (appBar != null && ViewCompat.isLaidOut(appBar)) {
                int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
                if (availableHeight == 0) {
                    availableHeight = parent.getHeight();
                }

                final int height = availableHeight - appBar.getMeasuredHeight();
                int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);

                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
                int childContentHeight = getContentHeight(child);

                if (childContentHeight <= height) {
                    updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, false);

                    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
                    parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);

                    return true;
                } else {
                    updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, true);

                    return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
                }
            }
        }

        return false;
    }

    private static int getContentHeight(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;

            int contentHeight = 0;
            for (int index = 0; index < viewGroup.getChildCount(); ++index) {
                View child = viewGroup.getChildAt(index);
                contentHeight += child.getMeasuredHeight();
            }
            return contentHeight;
        } else {
            return view.getMeasuredHeight();
        }
    }

    private static AppBarLayout findFirstAppBarLayout(List<View> views) {
        int i = 0;

        for (int z = views.size(); i < z; ++i) {
            View view = views.get(i);
            if (view instanceof AppBarLayout) {
                return (AppBarLayout) view;
            }
        }

        throw new IllegalArgumentException("Missing AppBarLayout in CoordinatorLayout dependencies");
    }

    private void updateToolbar(CoordinatorLayout parent, AppBarLayout appBar, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                               int heightUsed, boolean toggle) {
        toggleToolbarScroll(appBar, toggle);

        appBar.forceLayout();
        parent.onMeasureChild(appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    private void toggleToolbarScroll(AppBarLayout appBar, boolean toggle) {
        for (int index = 0; index < appBar.getChildCount(); ++index) {
            View child = appBar.getChildAt(index);

            if (child instanceof Toolbar) {
                Toolbar toolbar = (Toolbar) child;
                AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
                int scrollFlags = params.getScrollFlags();

                if (toggle) {
                    scrollFlags |= AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
                } else {
                    scrollFlags &= ~AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
                }

                params.setScrollFlags(scrollFlags);
            }
        }
    }
}

Ce comportement supprime essentiellement l'indicateur de défilement SCROLL de AppBarLayout lorsque le défilement du contenu dans la vue dépendante (RecyclerView, NestedScrollView) est inférieur à la hauteur de la vue, c'est-à-dire. lorsque le défilement n'est pas nécessaire. Elle annule également la vue de défilement décalée, qui est normalement effectuée par AppBarLayout.ScrollingViewBehavior. Fonctionne bien lorsque vous ajoutez un pied de page, c.-à-d. bouton, en mode défilement ou en ViewPager, la longueur du contenu pouvant être différente dans chaque page.

1
TrueCurry

J'ai fait quelque chose dans le sens suivant: j'ai ajouté Android:layout_gravity="end|bottom" À la mise en page XML souhaitée au bas de la CoordinatorLayout

et ensuite mettre dans le code:

 mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @SuppressLint("NewApi")
        @SuppressWarnings("deprecation")
        @Override
        public void onGlobalLayout() {
            if (mFooterView != null) {
                final int height = mFooterView.getHeight();
                mRecyclerView.setPadding(0, 0, 0, height);
                mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        }
    });

Remarque: le pied de page View/ViewGroup doit se situer plus haut sur l'axe z (répertorié sous RecyclerView en XML) pour fonctionner correctement.

0
AllDayAmazing

Je pense que créer un en-tête et un pied de page fixes pourrait résoudre votre problème. J'aurais écrit cela dans les commentaires mais je n'ai pas 50 répétitions. Vous pouvez trouver comment le faire ici

0
Anthony

Exemple de comportement de mise en page inférieure de coordinateur Android

activity_bottom.xml

<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.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimaryDark"
            app:layout_scrollFlags="scroll|enterAlways"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
    </Android.support.design.widget.AppBarLayout>

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/list"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:background="#C0C0C0"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.example.Android.coordinatedeffort.widget.FooterBarLayout
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:layout_gravity="bottom">

        <TextView
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="#007432"
            Android:gravity="center"
            Android:text="Footer View"
            Android:textColor="@Android:color/white"
            Android:textSize="25sp" />
    </com.example.Android.coordinatedeffort.widget.FooterBarLayout>

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

FooterBarLayout.Java

FooterBarBehavior.Java

0
Ahamadullah Saikat

Entourez vos éléments d'une couche linéaire, comme celle-ci:

<Android.support.design.widget.CoordinatorLayout >

  <LinearLayout
        Android:orientation="vertical"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent">

    <Android.support.design.widget.AppBarLayout>
      <Android.support.v7.widget.Toolbar />
    </Android.support.design.widget.AppBarLayout>
    <include layout="@layout/content_main" />

    </LinearLayout>

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