Wednesday, February 24, 2021

NestedScrollView and RecyclerView with physics-based bouncy overscroll effect

NestedScrollView and RecyclerView with physics-based bouncy overscroll effect

Bouncy

Add IOS-like overscroll animation to your scrolling views using SpringAnimation.

Currently includes BouncyRecyclerView and BouncyNestedScrollView.

Add Bouncy to your project

In your app module build.gradle:

 dependencies {

        implementation 'androidx.recyclerview:recyclerview:1.1.0'

        implementation 'com.factor:bouncy:1.61'

   }

(androidx.recyclerview is required for BouncyRecyclerView to work)

BouncyNestedScrollView

NestedScrollView with bouncy overscroll effect, currently only supports vertical scrolling.

Achieved by overriding the default EdgeEffect

Usage:

Use as normal NestedScrollView. Place it in your layout:

<com.factor.bouncy.BouncyNestedScrollView

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        app:fling_animation_size=".7"

        app:overscroll_animation_size=".7">


    <LinearLayout

            android:orientation="vertical"

            android:layout_width="match_parent"

            android:layout_height="wrap_content">

    </LinearLayout>

</com.factor.bouncy.BouncyNestedScrollView>

XML

fling_animation_size specifies the magnitude of overscroll effect for fling, default is 0.5 if no value is given.

overscroll_animation_size specifies the magnitude of overscroll effect for drag, default is 0.5 if no value is given.

Strongly suggest to keep both values lower than 5.

BouncyRecyclerView

BouncyRecyclerView adds overscroll effect to RecyclerView and supports drag & drop and swiping gestures

Usage:

Use as normal RecyclerView. Place it in your layout:

<com.factor.bouncy.BouncyRecyclerView

        android:id="@+id/recycler_view"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        app:recyclerview_fling_animation_size=".7"

        app:recyclerview_overscroll_animation_size=".7"

        app:recyclerview_damping_ratio="DAMPING_RATIO_LOW_BOUNCY"

        app:recyclerview_stiffness="STIFFNESS_MEDIUM"

        app:allow_drag_reorder="true"

        app:allow_item_swipe="false"/>

XML

set up layout manager and adapter. Theoratically supports any LayoutManager:

   recycler_view.setAdapter(myAdapter);

   recycler_view.setLayoutManager(new LinearLayoutManager(context));

   //recycler_view.setLayoutManager(new GridLayoutManager(context, 3));

Java

Customization

recyclerview_fling_animation_size specifies the magnitude of overscroll effect for fling, default is 0.5 if no value is given

recyclerview_overscroll_animation_size specifies the magnitude of overscroll effect for drag, default is 0.5 if no value is given

allow_drag_reorder and allow_item_swipe are set to false by default. If you would like to enable these features, simply set them to true.

Spring customization (Bouncy 1.6 and above)

recyclerview_damping_ratio and recyclerview_stiffness please refer to damping ratio and stiffness

Set in code:

   recycler_view.setFlingAnimationSize(0.3f);

   recycler_view.setOverscrollAnimationSize(0.3f);

   recycler_view.setDampingRatio(Bouncy.DAMPING_RATIO_HIGH_BOUNCY);

   recycler_view.setStiffness(Bouncy.STIFFNESS_HIGH);

Java

A known issue is when customizing spring properties, items close to the edges of the screen may be clipped since the current implementation animates the Y translation of the whole recyclerview. A workaround is to place the BouncyRecyclerView inside a NestedScrollView (not necessarily BouncyNestedScrollView):

<androidx.core.widget.NestedScrollView 

            android:layout_width="match_parent" 

            android:layout_height="match_parent">


        <!--setting damping ratio to HIGH_BOUNCY may result in items being clipped near the edges-->

        <com.factor.bouncy.BouncyRecyclerView

                android:id="@+id/rc"

                android:layout_width="match_parent"

                android:layout_height="match_parent"

                app:recyclerview_damping_ratio="DAMPING_RATIO_HIGH_BOUNCY"

                app:recyclerview_stiffness="STIFFNESS_LOW"

                app:allow_drag_reorder="true"

                app:allow_item_swipe="true"/>

        

    </androidx.core.widget.NestedScrollView>

XML

Drag & drop

Drag & drop does not work out of the box.

For drag & drop or swipe gestures to work, make your adapter extend BouncyRecyclerView.Adapter and add constructor matching parent.

(If your adapter does not extend BouncyRecyclerView.Adapter, BouncyRecyclerView will simply disable the gestures)

public class MyAdapter extends BouncyRecyclerView.Adapter

{

    private final ArrayList<MyData> dataSet;


    public MyAdapter(ArrayList<MyData> dataSet)

    {

        this.dataSet = dataSet;

    }


    @Override

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

    {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);

        return new MyViewHolder(view);

    }


    @Override

    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)

    {

        MyViewHolder h = (MyViewHolder) holder;

        h.getTextView().setText(dataSet.get(position).getData());

    }


    @Override

    public int getItemCount()

    {

        return dataSet.size();

    }


    @Override

    public void onItemMoved(int fromPosition, int toPosition)

    {

        //*****must override to save changes 

        //called repeatedly when item is dragged (reordered)

        

        //example of handling reorder

        MyData item = dataSet.remove(fromPosition);

        dataSet.add(toPosition, item);

        notifyItemMoved(fromPosition, toPosition);

    }


    @Override

    public void onItemSwipedToStart(RecyclerView.ViewHolder viewHolder, int position)

    {

        //item swiped left

    }


    @Override

    public void onItemSwipedToEnd(RecyclerView.ViewHolder viewHolder, int position)

    {

        //item swiped right

    }


    @Override

    public void onItemSelected(RecyclerView.ViewHolder viewHolder)

    {

        //item long pressed (selected)

    }


    @Override

    public void onItemReleased(RecyclerView.ViewHolder viewHolder)

    {

        //item released (unselected)

    }

}

Java

Also refer to the Kotlin example

No comments:

Post a Comment