AppBarLayout 中的工具栏是可滚动的,尽管回收视图没有足够的内容可滚动

AppBarLayout 中的 Toolbar 真的是可滚动的吗? 尽管具有“ appbar _ scrolling _ view _ actions”的主容器没有足够的内容来真正滚动?

到目前为止,我已经测试了:
当我使用 NestedScrollView (带有“ wrash _ content”属性)作为主容器,使用 TextView 作为子容器时,AppBarLayout 可以正常工作,不会滚动。

然而,当我使用一个只有几个条目和“ wrash _ content”属性(这样就不需要滚动)的回收视图时,AppBarLayout 中的工具栏是可滚动的,即使回收视图从未收到滚动事件(使用 OnScrollChangeListener 测试)。

这是我的布局代码:

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

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

我还找到了一种处理这个问题的方法,即检查所有回收视图项是否可见,并使用回收视图的 setNestedScrollingEnable ()方法。
尽管如此,在我看来,它确实更像一个 bug。有什么意见吗

编辑 # 1:

对于那些可能对我当前的解决方案感兴趣的人,我不得不把 setNestedScrollingEnable ()逻辑放在一个 Handler 的 postDelayed ()方法中,这个方法有5ms 的延迟,因为 LayoutManager 在调用这些方法时总是返回 -1,以确定第一个和最后一个项目是否可见。
我在 onStart ()方法中使用这段代码(在初始化回收视图之后) ,并在每次发生回收视图的内容更改之后使用。

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(甚至更早)中得到修复。因此,再也不需要变通方法了!

24085 次浏览

在你的 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>

我建议您尝试 这个示例,以支持设计库元素。

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

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

这不是 bug,viewGroup 中的所有事件都是这样处理的。因为您的回收视图是 CollaboratorLayout 的子视图,所以无论何时生成事件,都会首先检查它是否为父视图,如果父视图不感兴趣,则会将其传递给子视图。 参见 google 的文档

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循环再造图书馆(23.1.1)提供的任何 LayoutManager子类都没有做到这一点,这使我有些犹豫它是否是一个正确的解决方案; 它可能在本问题讨论的情况以外的其他情况下造成不希望的影响。

我已经使用我自己的行为类实现了它,这个类可能附加到 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)));

编辑2:

事实证明,在回收视图不可滚动时,确保工具栏不可滚动的唯一方法是以编程方式设置 setScrollFlags,这需要检查回收视图的设置是否可滚动。每次修改适配器时都必须执行这个检查。

与活动沟通的接口:

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>

测试片段:

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

资料来源:

编辑:

您应该使用 CollapsingToolbarLayout 来控制这种行为。

将工具栏直接添加到 AppBarLayout 中,可以访问 enterAlwaysCollapsed 和 exitUntilCollapsed 滚动标志,但是不能详细控制不同元素对崩溃的反应。 [ ... ]设置使用 CollapsingToolbarLayout 的应用程序: laypsingToolbarLayout: laypseMode = “ 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。但是,当你有一个可滚动的回收视图时,它并没有显示工具栏,而且它的最后一个项目是可见的(它不会在第一次向上滚动时显示工具栏) ,因此我决定修改它,使其更容易实现,并适用于动态适配器。

首先,您必须为您的应用程序栏创建一个自定义布局行为:

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, int type) {
updatedScrollable(directTargetChild);
return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}


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

然后,您只需要在布局文件中为您的应用程序栏定义这种行为:

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

我还没有测试它的屏幕旋转,所以让我知道它是否像这样工作。我想它应该可以工作,因为我认为当旋转发生时 count 变量不会被保存,但是如果没有保存,请让我知道。

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

谢谢,我创建了一个自定义的回收视图类,但关键仍然是使用 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);
}
}
}

我想给 User3623735的答案添加一些内容。下面的代码是完全不正确的。

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

即使有效,也不能涵盖所有情况。绝对不能保证数据以100 ms 的速度显示,而且数据可以在使用它的过程中拉伸视图的高度,而不仅仅是在 onCreateView 方法中。这就是为什么您应该使用下面的代码并跟踪视图高度的变化:

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(bottom != oldBottom)
{
mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
}
}
});

此外,不需要创建两个独立的方法来控制滚动状态,您应该使用一个 setScrollEnable 方法:

public void setScrollEnabled(boolean enabled) {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
mToolbar.getLayoutParams();


params.setScrollFlags(enabled ?
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);


mToolbar.setLayoutParams(params);
}