Android: el pie de página se desplaza cuando se usa en CoordinatorLayout

Tengo un AppBarLayout que desplaza la pantalla cuando se desplaza un RecyclerView . Debajo del RecyclerView hay un RelativeLayout que es un pie de página.

El pie de página se muestra sólo después de desplazarse hacia arriba – se comporta como lo ha hecho

 layout_scrollFlags="scroll|enterAlways" 

Pero no tiene ninguna banderas de desplazamiento – ¿es un error o estoy haciendo algo mal? Quiero que sea siempre visible

Antes de desplazarse

Introduzca aquí la descripción de la imagen

Después del desplazamiento

Introduzca aquí la descripción de la imagen

Actualizar

Abrió un problema de Google en esto – se marcó 'WorkingAsIntended' esto todavía no ayuda porque quiero una solución de trabajo de un pie de página dentro de un fragmento.

Actualización 2

Usted puede encontrar la actividad y el fragmento xmls aquí –

Tenga en cuenta que si la línea 34 en activity.xml – la línea que contiene la app:layout_behavior="@string/appbar_scrolling_view_behavior" se comenta el extremo del texto es visible desde el principio – de lo contrario, sólo es visible después de desplazarse hacia arriba

Utilizo una versión simplificada de la solución Learn OpenGL ES ( https://stackoverflow.com/a/33396965/778951 ), que mejora la solución de Noa ( https://stackoverflow.com/a/31140112/1317564 ). Funciona bien para mi simple barra de herramientas de retorno rápido encima de un TabLayout con botones de pie de página en el contenido ViewPager de cada pestaña.

Simplemente establezca FixScrollingFooterBehavior como el layout_behavior en el View / ViewGroup que desea mantener alineado en la parte inferior de la pantalla.

Diseño:

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

Comportamiento:

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

Actualizar

La solución siguiente no funciona para 5.1, ya que funciona en 5 – en lugar de getTop use getTranslationY en cualquiera de los cálculos que haga.

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

Actualización 2 con la nueva biblioteca de soporte – 22.2.1 – no hay diferencias entre las versiones 5.1 y anteriores, solo debe usar getTop e ignorar la actualización anterior en esta respuesta

Solución original Después de mirar en muchas direcciones resulta que la solución es realmente simple – añadir paddingBottom al fragmento y ajustarlo a medida que la página se desplaza.

El relleno es necesario para cubrir los cambios en la posición y de la barra de herramientas – el diseño del coordinador mueve toda la página hacia arriba y hacia abajo cuando la barra de herramientas desaparece y vuelve a aparecer.

Esto se puede lograr extendiendo AppBarLayout.ScrollingViewBehavior y estableciendo esto como el comportamiento del elemento fragmento de la actividad .

Aquí están los conceptos básicos del código – funciona para una actividad con sólo una barra de herramientas – puede reemplazarla por appbar.getTop() + toolbar.getHeight() y esto funcionará mejor si su appbar incluye pestañas .

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> 

Fragmento.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()); } 

Mi comportamiento

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

Comencé con la solución de Noa ( https://stackoverflow.com/a/31140112/1317564 ) y trabajó para los arrastres del dedo, pero me encontraba corriendo en problemas con los lanzamientos. Después de pasar algún tiempo para rastrear las llamadas de método y probar diferentes ideas, aquí está la solución que terminé con:

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

No soy fan de volver a revisar el diseño en cada marco de animación, y esta solución no es perfecta, ya que he visto algunos problemas si la expansión de programación / colapso del diseño de la barra de aplicaciones, pero por ahora no he encontrado una solución mejor . El rendimiento está bien en un dispositivo nuevo y aceptable en un dispositivo antiguo. Si alguien más lo hace, no dude en tomar mi respuesta como una fuente y repost.

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

Este comportamiento básicamente elimina la bandera de desplazamiento SCROLL de AppBarLayout , cuando el desplazamiento de contenido en la vista dependiente ( RecyclerView , NestedScrollView ) es menor que la altura de la vista, es decir. Cuando el desplazamiento no es necesario. También anula la vista desplazable de desplazamiento, que normalmente se realiza mediante AppBarLayout.ScrollingViewBehavior . Funciona bien al agregar pie de página, es decir. , A la vista de desplazamiento o en ViewPager , donde la longitud del contenido puede ser diferente en cada página.

Creo que la creación de un encabezado fijo y pie de página podría solucionar su problema. Me hubiera escrito esto en los comentarios, pero no tengo 50 representante. Podrías averiguar cómo hacerlo aquí

Hice algo a lo largo de las líneas de asegurar que agregué android:layout_gravity="end|bottom" al diseño en XML que quería en la parte inferior de la CoordinatorLayout

Y luego establecer en código:

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

Nota: el pie de página View / ViewGroup debe ser más alto en el eje z (que aparece debajo de RecyclerView en XML) para funcionar correctamente

Hay una biblioteca para su problema. Espero que esto ayude realmente para ti Aquí está la biblioteca

Y otro problema que usted ha mencionado fijó el pie de página. El siguiente es el diseño relativo, así que usa la función android:layout_alignParentBottom="true" en tu pie de página.

Espero que haya solucionado el problema

  • Transiciones de retorno de la actividad - Elementos compartidos - Actividad asesinada
  • TextInputLayout método setError lanza ClassCastException en 24.2.0
  • Android: varios snackbars en fragmentos separados (ViewPager)
  • ¿Cómo puedo ser notificado cuando una Snackbar se ha despedido?
  • El botón flotante no funciona en la versión Pre-lollipop
  • Biblioteca de diseño de Android CoordinatorLayout, AppBarLayout y DrawerLayout
  • Método onClick no funciona correctamente después de que NestedScrollView desplazado
  • Mi FloatingActionButton tiene algunas líneas extrañas que salen de él en 4.4 y menor
  • Cómo escalar la imagen en Android CollapsingToolbarLayout como WunderList hace
  • ArrayIndexOutOfBoundsException mientras descarta Snackbar / ViewDragHelper
  • Los niños de CoordinatorLayout no son de pantalla completa
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.