Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect nested fling stopped in CoordinatorLayout.Behavior?

How to detect if nested fling stopped entirely using CoordinatorLayout.Behavior? There is no such api which can give me callback when recycler view fling entirely stops.

like image 961
shekar Avatar asked Jun 23 '15 10:06

shekar


1 Answers

I started down this rabbit hole while trying to hide a Floating Action Button (FAB) when a RecyclerView was being scrolled. The proper way to do this according to multiple sources is to extend FloatingActionButton.Behavior, override the onStartNestedScroll and onStopNestedScroll methods, and hook your behavior up to the FAB, e.g. app:layout_behavior="com.justingarrick.ui.ScrollAwareFabBehavior". This works for normal (slow) scroll events, but onStopNestedScroll is not called when a fling ends.

Currently, there seem to be a number of open issues with flinging and scroll behaviors; the workaround for me was to implement an OnScrollListener for my RecyclerView and just change the state of the FAB programmatically, e.g.

public class MyFragment extends Fragment {

    @Bind(R.id.account_list) RecyclerView recyclerView;
    @Bind(R.id.button_fab) FloatingActionButton fab;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_accounts, container, false);
        ButterKnife.bind(this, view);

        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_DRAGGING)
                    fab.hide(); // or hideFab(), see below
                else if (newState == RecyclerView.SCROLL_STATE_IDLE)
                    fab.show(); // or showFab(), see below
            }
        });

        return view;
    }
}

UPDATE: This works correctly 99% of the time, but if you use the show() and hide() methods from version 22.2.1 of the design library, you'll run into problems when you try to scroll up at the top of your RecyclerView or down at the bottom of your RecyclerView because the recycler view is switching states from RecyclerView.SCROLL_STATE_DRAGGING to RecyclerView.SCROLL_STATE_IDLE so quickly that it creates a race condition in FloatingActionButtonHoneycombMr1#show(). So, to fix this (sigh), you either need to switch to setVisibility() calls if you don't care about the animations, or re-implement the animations without the race condition, e.g.

private void hideFab() {
     fab.animate().scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setDuration(200L).setInterpolator(new FastOutSlowInInterpolator()).setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
             fab.setVisibility(View.GONE);
         }
     });
 }

 private void showFab() {
     fab.animate().scaleX(1.0F).scaleY(1.0F).alpha(1.0F).setDuration(200L).setInterpolator(new FastOutSlowInInterpolator()).setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationStart(Animator animation) {
            fab.setVisibility(View.VISIBLE);
         }
     });
 }
like image 175
Justin Garrick Avatar answered Oct 05 '22 01:10

Justin Garrick