Android – Fußzeile scrollt außerhalb des Bildschirms, wenn sie in CoordinatorLayout verwendet wird

Ich habe ein AppBarLayout , das beim Scrollen einer RecyclerView vom Bildschirm scrollt. Unter dem RecyclerView befindet sich ein RelativeLayout , das eine Fußzeile darstellt.

Die Fußzeile wird erst nach dem Scrollen angezeigt – sie verhält sich wie sie ist

 layout_scrollFlags="scroll|enterAlways" 

aber es hat keine Scroll-Flags – ist es ein Bug oder mache ich etwas falsch? Ich möchte, dass es immer sichtbar ist

vor dem Scrollen

Bildbeschreibung hier eingeben

nach dem Blättern

Bildbeschreibung hier eingeben

Aktualisieren

Ich habe ein Google-Problem auf diesem Thema – es wurde markiert “WorkingAsIntended” dies immer noch nicht helfen, weil ich eine funktionierende Lösung einer Fußzeile in einem Fragment wollen.

Update 2

Sie können die Aktivität und das Fragment xmls hier finden –

Beachten Sie, dass, wenn Zeile 34 in activity.xml – die Zeile, die app:layout_behavior="@string/appbar_scrolling_view_behavior" ist, das app:layout_behavior="@string/appbar_scrolling_view_behavior" von Anfang an sichtbar ist – andernfalls ist es erst nach dem Scrollen sichtbar

    Ich verwende eine vereinfachte Version der Learn OpenGL ES-Lösung ( https://Stackoverflow.com/a/33396965/778951 ) – die die Lösung von Noa verbessert ( https://Stackoverflow.com/a/31140112/1317564 ). Es funktioniert gut für meine einfache Quick-Return-Toolbar über einem TabLayout mit Fußtastern in jedem ViewPager Inhalt der Registerkarte.

    Setzen Sie FixScrollingFooterBehavior als Layout_Behavior für die View / ViewGroup, die Sie am unteren Bildschirmrand ausrichten möchten.

    Layout:

     < ?xml version="1.0" encoding="utf-8"?>        

    Verhalten:

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

    Aktualisieren

    Die folgende Lösung funktioniert nicht für 5.1, da sie in 5 funktioniert – anstelle von getTop verwenden Sie getTranslationY in einer der Berechnungen, die Sie durchführen.

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

    Update 2 mit der neuen Support-Bibliothek – 22.2.1 – Es gibt keinen Unterschied zwischen 5.1 und vorherigen Versionen, Sie sollten nur getTop verwenden und die vorherige Aktualisierung in dieser Antwort ignorieren

    Ursprüngliche Lösung Nach dem Blick in viele Richtungen stellt sich heraus, dass die Lösung eigentlich einfach ist: Fügen Sie paddingBottom zum Fragment hinzu und passen Sie es an, wenn die Seite scrollt.

    Das Padding wird benötigt, um die Änderungen in der y- Position der Symbolleiste abzudecken – das Layout des Koordinators bewegt die gesamte Seite nach oben und unten, wenn die Symbolleiste verschwindet und wieder erscheint.

    Dies kann erreicht werden, indem Sie AppBarLayout.ScrollingViewBehavior und dies als Verhalten des Fragmentelements der Aktivität AppBarLayout.ScrollingViewBehavior .

    Hier sind die Grundlagen des Codes – es funktioniert für eine Aktivität mit nur einer Symbolleiste – Sie können es durch appbar.getTop() + toolbar.getHeight() ersetzen und dies funktioniert besser, wenn Ihre Appbar Registerkarten enthält .

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

    Mein Benehmen

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

    Ich begann mit Noa’s Lösung ( https://Stackoverflow.com/a/31140112/1317564 ) und es funktionierte für Finger-Drag, aber ich hatte Probleme mit Flings. Nachdem ich einige Zeit aufgewendet habe, um die Methodenaufrufe zu verfolgen und verschiedene Ideen auszuprobieren, hier ist die Lösung, mit der ich endete:

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

    Ich bin kein Fan davon, das Layout in jedem Animationsframe zu überprüfen, und diese Lösung ist nicht perfekt, da ich einige Probleme beim programmatischen Erweitern / Reduzieren des Layouts der App-Leiste gesehen habe, aber im Moment habe ich keine bessere Lösung gefunden . Die performance ist auf einem neuen Gerät in Ordnung und auf einem älteren Gerät akzeptabel. Wenn jemand anderes dies tut, bitte zögern Sie nicht, meine Antwort als Quelle zu nehmen und erneut zu veröffentlichen.

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

    Dieses Verhalten entfernt im Grunde Scroll-Flag SCROLL von AppBarLayout , wenn Scroll-Inhalt in abhängiger Ansicht ( RecyclerView , NestedScrollView ) ist weniger als Ansichtshöhe, dh. wenn das Scrollen nicht benötigt wird. Es überschreibt auch die Offset-Scroll-Ansicht, die normalerweise von AppBarLayout.ScrollingViewBehavior . functioniert gut beim Hinzufügen von Fußzeilen, dh. oder in ViewPager , wo die Inhaltslänge auf jeder Seite unterschiedlich sein kann.

    Ich denke, das Erstellen einer festen Kopf- und Fußzeile könnte Ihr Problem lösen. Ich hätte dies in den Kommentaren geschrieben, aber ich habe keine 50 Wiederholungen. Sie könnten herausfinden, wie es hier geht

    Ich habe etwas getan, um sicherzugehen, dass ich android:layout_gravity="end|bottom" zu dem Layout in XML hinzugefügt habe, das ich am unteren Rand des CoordinatorLayout

    und dann im Code festlegen:

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

    Hinweis: Die Fußzeile View / ViewGroup muss auf der z-Achse (unterhalb der RecyclerView in XML aufgeführt) höher sein, damit sie ordnungsgemäß funktioniert

    Umgeben Sie Ihre Elemente mit einem linearen Layout, so:

             

    Android CoordinatorLayout Bottom Layout-Verhaltensbeispiel

    activity_bottom.xml

              

    FooterBarLayout.java

    FooterBarBehavior.java

    Es gibt eine Bibliothek für dein Problem. Hoffe das wird dir wirklich helfen Hier ist die Bibliothek

    Und ein anderes Problem, das du erwähnt hast, hat die Fußzeile repariert. Das folgende ist das relative Layout, also benutze das Feature android:layout_alignParentBottom="true" in deiner Fußzeile.

    Ich hoffe, ich habe das Problem getriggers