antonicg
antonicg

Reputation: 944

Custom CoordinatorLayout.Behavior and RecyclerView scroll issue

I have a CoordinatorLayout with two children, a View that acts as header and a RecyclerView:

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center_horizontal"
        app:layout_behavior="some.package.AlphaBehavior">

        <ImageView
            android:id="@+id/header_iv"
            style="@style/some_style"/>

        <TextView
            android:id="@+id/header_retails_tv"
            style="@style/some_style_tv"
            android:text="@string/some_text"/>

        </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false" />

</android.support.design.widget.CoordinatorLayout>

I set a padding dynamically to RecyclerView with the size of header and I set the clipToPadding to false, so the RecyclerView is displayed below the header and when the user makes scroll up, the RecyclerView is shown above the header view.

I made a custom CoordinatorLayout.Behavior in order to accomplish a fade out of the view when the user scrolls up the list and a fade in when the header have to be visible again, the AlphaBehavior:

public class AlphaBehavior extends CoordinatorLayout.Behavior {

    private float alpha                 = 1.0f;
    private float scrolly               = 0.f;
    private int headerSize = 0;

    private Animation defaultFadeInAnimation;

    public AlphaBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);


        defaultFadeInAnimation = AnimationUtils.loadAnimation(context, android.R.anim.fade_in);
    }

    public void setHeaderSize(int headerSize) {
        this.headerSize = headerSize;
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof RecyclerView;
    }


    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

        scrolly += dyConsumed;

        Log.d(Constants.TAG, dyConsumed + "/" + dyUnconsumed + "/" + scrolly);

        float totalScrollY = ((RecyclerView)target).computeVerticalScrollOffset();

        Log.d(Constants.TAG, "totalScrollY:" + totalScrollY);

        alpha = (headerSize - totalScrollY) / headerSize;

        if (alpha < 0.f) alpha = 0.f;
        if (alpha > 1.0f) alpha = 1.f;

        if (dyConsumed < 0 && totalScrollY > headerSize) {
            alpha = 0.f;
        }

        Log.d(Constants.TAG, "alpha:" + alpha);

        child.setAlpha(alpha);
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {

        int pos = ((LinearLayoutManager)((RecyclerView)target).getLayoutManager()).findFirstCompletelyVisibleItemPosition();

        Log.d(Constants.TAG, "pos:" + pos);

        if (pos == 0 && child.getAlpha() == 0.f) {
            child.startAnimation(defaultFadeInAnimation);
        }
    }



    // overriding this in case we don't the other events are not called
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }
}

But I'm facing an issue: if the user make scroll very fast the events of the behaviour are not calling correctly. The scrollY member is not well-related to the total scroll and the totalScrollY member (obtained from computing the scroll from RecyclerView) is not correct. Even I tried to find the firstCompletelyVisibleItem in the onStopNestedScroll event, but it is returning the position 2 or 3 when the recyclerView achieves the start of the list.

Upvotes: 3

Views: 1719

Answers (1)

antonicg
antonicg

Reputation: 944

Finally, I solved it using an OnScrollListener instead of using a CoordinatorLayout.Behavior and it is working like a charm. I put the code, maybe is useful for someone:

The custom onScrollListener to hide a view:

public class HideViewOnScrollListener extends RecyclerView.OnScrollListener {

    private float alpha = 1.f;
    private float scrolly = 0.f;

    private int heightViewToHide;
    private final View viewToHide;

    public HideViewOnScrollListener(View viewToHide) {
        this.viewToHide = viewToHide;

        heightViewToHide = viewToHide.getHeight();
        if (heightViewToHide == 0) {

            ViewTreeObserver viewTreeObserver = viewToHide.getViewTreeObserver();
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {

                    heightViewToHide = viewToHide.getHeight();

                    if (heightViewToHide > 0)
                        viewToHide.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            });
        }

    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        scrolly += dy;

        alpha = (heightViewToHide - scrolly) / heightViewToHide;

        if (alpha < 0.f) alpha = 0.f;
        if (alpha > 1.0f) alpha = 1.f;

        if (dy < 0 && scrolly > heightViewToHide) {
            alpha = 0.f;
        }

        viewToHide.setAlpha(alpha);
    }
}

And you can add to a RecyclerView that way:

recyclerView.addOnScrollListener(new HideViewOnScrollListener(viewToHide));

Upvotes: 4

Related Questions