在CoordinatorLayout中使用时,Android页脚会滚动屏幕

滚动RecyclerView时,我有一个AppBarLayout滚动屏幕。 在RecyclerView下面有一个RelativeLayout ,它是一个页脚。

页脚只有在滚动后才显示 – 它的行为就像它一样

 layout_scrollFlags="scroll|enterAlways" 

但它没有任何滚动标志 – 这是一个错误,或者我做错了什么? 我希望它始终可见

在滚动之前

在这里输入图像说明

滚动后

在这里输入图像说明

更新

在这个问题上打开了一个谷歌问题 – 它被标记为“WorkingAsIntended”这仍然没有帮助,因为我想要一个片段内的页脚的工作解决scheme。

更新2

你可以在这里find活动和片段xmls –

请注意,如果activity.xml第34行(包含app:layout_behavior="@string/appbar_scrolling_view_behavior"被注释掉了,文本结束从开始就是可见的),否则只有在滚动

我使用Learn OpenGL ES的解决scheme( https://stackoverflow.com/a/33396965/778951 )的简化版本 – 这改进了Noa的解决scheme( https://stackoverflow.com/a/31140112/1317564 )。 它适用于TabLayout上方的简单快速返回工具栏,每个选项卡的ViewPager内容都带有页脚button。

只需将FixScrollingFooterBehavior设置为View / ViewGroup上的layout_behavior,并将其保留在屏幕的底部。

布局:

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

行为:

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

更新

下面的解决scheme不适用于5.1,因为它在5中工作 – 而不是getTop在任何计算中使用getTranslationY

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

更新2与新的支持库 – 22.2.1 – 5.1和prev版本之间没有差异,你应该只使用getTop并忽略此答案中的以前的更新

原始解决scheme看了很多方向后,解决scheme实际上很简单 – 添加paddingBottom到片段,并在页面滚动时进行调整。

需要填充以覆盖工具栏y位置的变化 – 协调器布局在工具栏消失并重新出现时将整个页面上下移动。

这可以通过扩展AppBarLayout.ScrollingViewBehavior并将其设置为活动片段元素的行为来实现。

这里是代码的基础知识 – 它适用于只有一个工具栏的活动 – 您可以用appbar.getTop() + toolbar.getHeight()replace它,如果您的appbar包含选项卡 ,这将工作得更好。

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

我开始使用Noa的解决scheme( https://stackoverflow.com/a/31140112/1317564 ),它用于手指拖动,但我徘徊遇到麻烦。 花了一些时间来追踪方法调用并尝试不同的想法后,下面是我最终的解决scheme:

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

我不是在每个animation框架上重新检查布局的粉丝,这个解决scheme并不完美,因为我已经看到一些问题,如果以编程方式展开/折叠应用程序栏布局,但现在我还没有find更好的解决scheme。 在新设备上性能良好,在旧设备上可以接受。 如果有其他人,请随时把我的答案作为来源和转发。

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

当从属视图( RecyclerViewNestedScrollView )中的滚动内容小于视图高度时,这种行为基本上从AppBarLayout移除滚动标志SCROLL 。 当不需要滚动时。 它也覆盖抵消滚动视图,这通常由AppBarLayout.ScrollingViewBehavior完成。 当添加页脚,即。 button,滚动视图或在ViewPager ,每个页面的内容长度可能不同。

我认为创build一个固定的页眉和页脚可以解决您的问题。 我会在评论中写下这个,但是我没有50个代表。 你可以弄清楚如何在这里做

我沿着确保的方式做了一些事情android:layout_gravity="end|bottom"我在CoordinatorLayout的底部添加了android:layout_gravity="end|bottom"到XML格式的布局

然后在代码中设置:

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

注意:页脚View / ViewGroup需要在Z轴上列出(在XML中的RecyclerView下面列出)才能正常工作

有问题的图书馆。 希望这会对你有帮助这是图书馆

而你提到的另一个问题是固定页脚。 下面的是相对的布局,所以使用functionandroid:layout_alignParentBottom="true"在您的页脚。

希望你我已经解决了这个问题