I have a fairly typical List functionality using a CoordinatorLayout, AppBarLayout, SwipeRefreshLayout and RecyclerView -
When the RecyclerView has enough content to scroll, the page seems fine. When the RecyclerView is empty or doesn't have enough content to scroll however, the behavior is that the AppBarLayout children with app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
will continue to scroll - which looks odd.
Is there a way to stop the AppBarLayout children scrolling when the NestedScrollView is empty?
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:elevation="4dp">
<LinearLayout
android:id="@+id/eventHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/green"
android:orientation="horizontal"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="scroll|enterAlwaysCollapsed"
android:textColor="@color/white"
android:textSize="15sp"/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeToRefresh"
android:layout_width="match_parent"
android:layout_gravity="fill_vertical"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:dividerHeight="0dp"
android:layout_gravity="fill_vertical"
android:drawSelectorOnTop="true"
android:listSelector="@drawable/selector_ripple_grey_transparent"
android:scrollbars="vertical"/>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
<TextView
android:id="@+id/noData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:text="@string/no_data_available"
android:textSize="17sp"/>
</FrameLayout>
Not sure how elegant a solution this is but, I overrode the onStartNestedScroll()
event to only fire if the NestedScrollView is scrollable (In this case a RecyclerView)
in onCreate():
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(mAppBarLayout, mRecyclerView));
Behavior:
public class AppBarLayoutNoEmptyScrollBehavior extends AppBarLayout.Behavior {
AppBarLayout mAppBarLayout;
RecyclerView mRecyclerView;
public AppBarLayoutNoEmptyScrollBehavior(AppBarLayout appBarLayout, RecyclerView recyclerView) {
mAppBarLayout = appBarLayout;
mRecyclerView = recyclerView;
}
public boolean isRecylerViewScrollable(RecyclerView recyclerView) {
int recyclerViewHeight = recyclerView.getHeight(); // Height includes RecyclerView plus AppBarLayout at same level
int appCompatHeight = mAppBarLayout != null ? mAppBarLayout.getHeight() : 0;
recyclerViewHeight -= appCompatHeight;
return recyclerView.computeVerticalScrollRange() > recyclerViewHeight;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecylerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
EDIT
Edited solution as RecyclerView gives height as visible RecyclerView height and AppBarLayout height (which is the CoordinatorLayout height).
However, if your scroll gesture starts on the visible AppBarLayout area, a scroll will still take place, even if you add this Behavior to the AppBarLayout as well. This answer therefore is not a fix for the problem.
(Based on : Reference)
(1) Create this class.
public class AppBarLayoutBehaviorForEmptyRecyclerView extends AppBarLayout.Behavior
{
private boolean canRecyclerViewBeScrolled = false;
public AppBarLayoutBehaviorForEmptyRecyclerView()
{
}
public AppBarLayoutBehaviorForEmptyRecyclerView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev)
{
return canRecyclerViewBeScrolled && super.onInterceptTouchEvent(parent, child, ev);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes)
{
updateScrollable(target);
return canRecyclerViewBeScrolled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed)
{
return canRecyclerViewBeScrolled && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updateScrollable(View targetChild)
{
if(targetChild instanceof RecyclerView)
{
RecyclerView.Adapter adapter = ((RecyclerView) targetChild).getAdapter();
canRecyclerViewBeScrolled = adapter != null && adapter.getItemCount() > 0;
}
else
{
canRecyclerViewBeScrolled = true;
}
}
}
(2) Add to your AppBarLayout
XML element the following attribute:
app:layout_behavior="com.xxxx.xxxxxx.AppBarLayoutBehaviorForEmptyRecyclerView"
Graeme answer is ok but I also added in constructor
public AppBarLayoutOnEmptyRecyclerViewScrollBehavior(@NonNull AppBarLayout appBarLayout, @NonNull RecyclerView recyclerView) {
this.appBarLayout = checkNotNull(appBarLayout);
this.recyclerView = checkNotNull(recyclerView);
setDragCallback(new DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
return isRecylerViewScrollable(recyclerView);
}
});
}
so when RecyclerView
is empty I also disable drag from AppBarLayout
After loading data to your RecyclerView, if it's empty, just disalbe the scroll flag manually:
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mEventHeader.getLayoutParams();
toolbarLayoutParams.setScrollFlags(0);
mEventHeader.setLayoutParams(toolbarLayoutParams);
And if not empty, set back the scroll flag:
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mEventHeader.getLayoutParams();
toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
mEventHeader.setLayoutParams(toolbarLayoutParams);
来源:https://stackoverflow.com/questions/35225134/stop-appbarlayout-scrolling-off-screen-when-nestedscrollview-is-empty