尽pipeRecyclerView没有足够的内容滚动,但AppBarLayout中的工具栏是可滚动的

是否真的打算在AppBarLayout的工具栏可滚动,虽然“appbar_scrolling_view_behavior”主容器没有足够的内容真正滚动?

到目前为止我所testing的是:
当我使用一个NestedScrollView(“wrap_content”属性)作为主容器和一个TextView作为子,AppBarLayout工作正常,不滚动。

但是,当我使用RecyclerView只有几个条目和“wrap_content”属性(以便不需要滚动)时,即使RecyclerView从未收到滚动事件(使用OnScrollChangeListenertesting),AppBarLayout中的工具栏也是可滚动的)。

这是我的布局代码:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBarLayout" 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/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:theme="@style/ToolbarStyle" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout> 

有以下效果,工具栏是可滚动的,虽然它不是必需的:

我还find了一个方法来处理这个问题,通过检查所有的RecyclerView项是否可见并使用RecyclerView的setNestedScrollingEnabled()方法。
尽pipe如此,它看起来更像是一个打算给我的bug。 有什么意见? :d

编辑#1:

对于可能对我当前的解决scheme感兴趣的人,由于LayoutManager在调用方法来查找时始终返回-1,所以必须将setNestedScrollingEnabled()逻辑置于Handler的postDelayed()方法中,延迟时间为5 ms是否第一个和最后一个项目是可见的。
我在onStart()方法中使用这段代码(在我的RecyclerView被初始化之后),每次发生RecyclerView的内容更改之后。

 final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager(); new Handler().postDelayed(new Runnable() { @Override public void run() { //no items in the RecyclerView if (mRecyclerView.getAdapter().getItemCount() == 0) mRecyclerView.setNestedScrollingEnabled(false); //if the first and the last item is visible else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0 && layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1) mRecyclerView.setNestedScrollingEnabled(false); else mRecyclerView.setNestedScrollingEnabled(true); } }, 5); 

编辑#2:

我刚刚玩了一个新的应用程序,似乎这(意外的)行为已被修复支持库版本23.3.0(甚至更早)。 因此,不再需要解决方法!

编辑2:

当RecyclerView不可滚动时,确保工具栏不可滚动的唯一方法是以编程方式设置setScrollFlags,它需要检查RecyclerView是否可滚动。 每次修改适配器时都必须执行此检查。

与活动交stream的界面:

 public interface LayoutController { void enableScroll(); void disableScroll(); } 

主要活动:

 public class MainActivity extends AppCompatActivity implements LayoutController { private CollapsingToolbarLayout collapsingToolbarLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); final FragmentManager manager = getSupportFragmentManager(); final Fragment fragment = new CheeseListFragment(); manager.beginTransaction() .replace(R.id.root_content, fragment) .commit(); } @Override public void enableScroll() { final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams(); params.setScrollFlags( AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS ); collapsingToolbarLayout.setLayoutParams(params); } @Override public void disableScroll() { final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams(); params.setScrollFlags(0); collapsingToolbarLayout.setLayoutParams(params); } } 

activity_main.xml中:

 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_height="match_parent" android:layout_width="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_content" 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:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <FrameLayout android:id="@+id/root_content" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="fill_vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </android.support.design.widget.CoordinatorLayout> </android.support.v4.widget.DrawerLayout> 

testing片段:

 public class CheeseListFragment extends Fragment { private static final int DOWN = 1; private static final int UP = 0; private LayoutController controller; private RecyclerView rv; @Override public void onAttach(Context context) { super.onAttach(context); try { controller = (MainActivity) getActivity(); } catch (ClassCastException e) { throw new RuntimeException(getActivity().getLocalClassName() + "must implement controller.", e); } } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rv = (RecyclerView) inflater.inflate( R.layout.fragment_cheese_list, container, false); setupRecyclerView(rv); // Find out if RecyclerView are scrollable, delay required final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) { controller.enableScroll(); } else { controller.disableScroll(); } } }, 100); return rv; } private void setupRecyclerView(RecyclerView recyclerView) { final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext()); recyclerView.setLayoutManager(layoutManager); final SimpleStringRecyclerViewAdapter adapter = new SimpleStringRecyclerViewAdapter( getActivity(), // Test ToolBar scroll getRandomList(/* with enough items to scroll */) // Test ToolBar pin getRandomList(/* with only 3 items*/) ); recyclerView.setAdapter(adapter); } } 

资料来源:

  • 以编程方式更改滚动标志
  • Chris Banes的原始代码
  • 需要一个postdelayed来确保RecyclerView的孩子已经准备好进行计算了

编辑:

你应该CollapsingToolbarLayout来控制行为。

将工具栏直接添加到AppBarLayout可让您访问enterAlwaysCollapsed和exitUntilCollapsed滚动标志,但不能详细控制不同元素对折叠的反应。 设置使用CollapsingToolbarLayout的应用程序:layout_collapseMode =“pin”,以确保在视图崩溃时,工具栏本身保持固定在屏幕的顶部。 http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html

 <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android:id="@+id/drawer_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> 

 app:layout_collapseMode="pin" 

到xml中的工具栏。

  <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:layout_collapseMode="pin" app:theme="@style/ToolbarStyle" /> 

所以,适当的信用,这个答案几乎解决了我的https://stackoverflow.com/a/32923226/5050087 。 但是因为当你实际上有一个可滚动的回收站,并且它的最后一个项目是可见的(它不会在第一个向上滚动时显示工具栏)时,它并没有显示工具栏,所以我决定修改它并使其更容易实现,适配器。

首先,您必须为您的appbar创build自定义布局行为:

 public class ToolbarBehavior extends AppBarLayout.Behavior{ private boolean scrollableRecyclerView = false; private int count; public ToolbarBehavior() { } public ToolbarBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) { return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev); } @Override public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { updatedScrollable(directTargetChild); return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); } @Override public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) { return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed); } private void updatedScrollable(View directTargetChild) { if (directTargetChild instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) directTargetChild; RecyclerView.Adapter adapter = recyclerView.getAdapter(); if (adapter != null) { if (adapter.getItemCount()!= count) { scrollableRecyclerView = false; count = adapter.getItemCount(); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager != null) { int lastVisibleItem = 0; if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition()); } else if (layoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]); lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]); } scrollableRecyclerView = lastVisibleItem < count - 1; } } } } else scrollableRecyclerView = true; } } 

然后,你只需要在你的布局文件中为你的appbar定义这个行为:

 <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior" > 

我没有testing它的屏幕旋转,所以让我知道,如果它的作品是这样的。 我想这应该工作,因为我不认为计数variables保存时发生的旋转,但让我知道如果它不。

这是对我来说最简单和最干净的实施,享受它。

这不是一个bug,viewGroup中的所有事件都是这样处理的。 由于您的recyclerview是coordinatorLayout的子级,所以无论何时生成事件,都会首先检查父级,如果父级不感兴趣,则只传递给子级。 请参阅谷歌文档

LayoutManager子类中类似这样的东西似乎导致所需的行为:

 @Override public boolean canScrollVertically() { int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition(); if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false; int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition(); if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false; if (firstCompletelyVisibleItemPosition == 0 && lastCompletelyVisibleItemPosition == getItemCount() - 1) return false; return super.canScrollVertically(); } 

canScrollVertically()的文档说:

 /** * Query if vertical scrolling is currently supported. The default implementation * returns false. * * @return True if this LayoutManager can scroll the current contents vertically */ 

注意“可以垂直滚动当前内容 ”的措辞,我相信这意味着当前状态应该由返回值来反映。

然而,这并不是通过v7 recyclerview库(23.1.1)提供的任何LayoutManager子类来完成的,这使我在某种程度上是否是一个正确的解决scheme犹豫不决; 在这个问题讨论的其他情况下可能会造成不良影响。

我已经实现它使用我自己的行为类,可能会附加到AppBarLayout:

 public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior { private RecyclerView recyclerView; private int additionalHeight; public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) { this.recyclerView = recyclerView; this.additionalHeight = additionalHeight; } public boolean isRecyclerViewScrollable(RecyclerView recyclerView) { return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight); } @Override public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { if (isRecyclerViewScrollable(mRecyclerView)) { return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); } return false; } 

}

下面是代码如何设置这个行为:

 final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView(); CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height))); 

在你的Toolbar删除scroll标志,只留下enterAlways标志,你应该得到你想要的效果。 为了完整性,您的布局应该如下所示:

 <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBarLayout" 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/colorPrimary" app:layout_scrollFlags="enterAlways" app:theme="@style/ToolbarStyle" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout> 

我build议你尝试这个样本,以支持devise库元素。

这个布局就像你在样本中的布局。

 <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_content" 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:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_scrollFlags="scroll|enterAlways" /> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> </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="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout> 

谢谢,我创build了一个RecyclerView的自定义类,但关键仍然使用setNestedScrollingEnabled() 。 它在我身边很好。

 public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener { public RecyclerViewCustom(Context context) { super(context); } public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * This supports scrolling when using RecyclerView with AppbarLayout * Basically RecyclerView should not be scrollable when there's no data or the last item is visible * * Call this method after Adapter#updateData() get called */ public void addOnGlobalLayoutListener() { this.getViewTreeObserver().addOnGlobalLayoutListener(this); } @Override public void onGlobalLayout() { // If the last item is visible or there's no data, the RecyclerView should not be scrollable RecyclerView.LayoutManager layoutManager = getLayoutManager(); final RecyclerView.Adapter adapter = getAdapter(); if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null) { setNestedScrollingEnabled(false); } else { int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition(); boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1; setNestedScrollingEnabled(!isLastItemVisible); } unregisterGlobalLayoutListener(); } private void unregisterGlobalLayoutListener() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { getViewTreeObserver().removeGlobalOnLayoutListener(this); } } }