Stop AppBarLayout scrolling off screen when NestedScrollView is empty

耗尽温柔 提交于 2019-11-30 08:30:56

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.

OneEyeQuestion

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

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!