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

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

El pie de página se muestra solo después de desplazarse hacia arriba, se comporta como si tuviera

 layout_scrollFlags="scroll|enterAlways" 

pero no tiene ninguna bandera de desplazamiento, ¿es un error o estoy haciendo algo mal? Quiero que sea siempre visible

antes del desplazamiento

enter image description here

después del desplazamiento

enter image description here

Actualizar

Abrí un problema de Google en esto – estaba marcado como “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

puedes 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" está comentada, el texto final es visible desde el inicio; de lo contrario, solo 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 barra de herramientas simple de devolución rápida sobre un TabLayout con botones de pie de página en el contenido ViewPager de cada pestaña.

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

Diseño:

         

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 a continuación 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 prev, 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: agregue paddingBottom al fragmento y ajústelo a medida que la página se desplaza.

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

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

Estos son los conceptos básicos del código: funciona para una actividad con solo una barra de herramientas. Puede reemplazarlo con appbar.getTop() + toolbar.getHeight() y esto funcionará mejor si su barra de aplicaciones incluye tabs .

activity.xml

       

fragment.xml

     

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

Empecé con la solución de Noa ( https://stackoverflow.com/a/31140112/1317564 ) y funcionó para arrastrar los dedos, pero estaba teniendo problemas con los aventuras. Después de pasar un tiempo para rastrear las llamadas al método y probar diferentes ideas, esta es la solución con la que terminé:

 // 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 partidario de volver a verificar el diseño en cada cuadro de animación, y esta solución no es perfecta ya que he visto algunos problemas al expandir / colapsar programáticamente el diseño de la barra de aplicaciones, pero por ahora no he encontrado una solución mejor . El rendimiento es bueno en un dispositivo nuevo y aceptable en un dispositivo anterior. Si alguien más lo hace, no dude en tomar mi respuesta como fuente y volver a publicar.

 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 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 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 AppBarLayout 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 desplazada de desplazamiento, que normalmente se realiza mediante AppBarLayout.ScrollingViewBehavior . Funciona bien al agregar pie de página, es decir. botón, para desplazarse por la vista o en ViewPager , donde la longitud del contenido puede ser diferente en cada página.

Creo que crear un encabezado y pie de página fijo podría resolver su problema. Hubiera escrito esto en los comentarios, pero no tengo 50 rep. Podrías descubrir cómo hacerlo aquí

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

y luego establecer en el 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: que la vista / grupo de vista del pie de página debe ser más alta en el eje z (enumerada debajo de RecyclerView en XML) para funcionar correctamente

Rodea tus elementos con una distribución lineal, así:

         

Ejemplo de comportamiento de Layout inferior de Android CoordinatorLayout

activity_bottom.xml

          

FooterBarLayout.java

FooterBarBehavior.java

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

Y otro problema que ha mencionado solucionó el pie de página. el de abajo es el diseño relativo así que use la función android:layout_alignParentBottom="true" en su pie de página.

Espero que hayas resuelto el problema