Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS like over scroll effect on Android

Tags:

I want to implement the iOS-like bounce overscroll effect in my app.

I came across this link which suggests creating a custom ScrollView. But the problem is that when I am scrolling up and down fast it's working fine but as soon as I pull the bottom or top of the screen it's just stuck and the effect is not working anymore.

As an example of the kind of animation I want to achieve you can look at this:

This is the code I currently have:

public class ObservableScrollView extends ScrollView
{
    private static final int MAX_Y_OVERSCROLL_DISTANCE = 150;

    private Context mContext;
    private int mMaxYOverscrollDistance;

    public ObservableScrollView(Context context)
    {
        super(context);
        mContext = context;
        initBounceScrollView();
    }

    public ObservableScrollView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mContext = context;
        initBounceScrollView();
    }

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        mContext = context;
        initBounceScrollView();
    }

    private void initBounceScrollView()
    {
        //get the density of the screen and do some maths with it on the max overscroll distance
        //variable so that you get similar behaviors no matter what the screen size

        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
        final float density = metrics.density;

        mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
    {
        //This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverscrollDistance;
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);
    }
}
like image 655
Star Avatar asked Nov 23 '16 07:11

Star


People also ask

How do you scroll screen on Android?

Use the usual button combination (Power + Volume down) to capture a screenshot. On the left side of your screen, you'll see a thumbnail of your screenshot with a button below it called Scroll. Tap on it. Allow the screen to scroll to the point you want it to and tap on the screen to stop.

Can scroll vertically Android?

Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project. Step 2 − Add the following code to res/layout/activity_main. xml. In this above code, we have declare Linear layout as parent and added Vertical Scroll view.

Which component is used to add a scrolling effect in the app?

Components that contain built-in scrolling implementationsListView. GridView. ScrollView. NestedScrollView.


3 Answers

I have quickly put together a simple solution based on a CoordinatorLayout.Behavior. It's not perfect, you can maybe spend some time fine tuning it a bit, but it's not bad. Anyway the result should look something like this:

enter image description here

As a small side note before I start with the answer: I strongly recommend that you use the NestedScrollView from the support library instead of a normal ScrollView. They are identical in any way, but the NestedScrollView implements correct nested scrolling behaviour on lower API levels.

Anyway let's start with my answer: The solution I came up with would work with any scrollable container, be it a ScrollView, ListView or RecyclerView and you don't need to subclass any Views to implement it.

First you need to add Google's Design Support Library to your project if you aren't already using it:

compile 'com.android.support:design:25.0.1'

Remember that if you aren't targeting API level 25 (which you should by the way) then you need to include the newest version for your API level (eg. compile 'com.android.support:design:24.2.0' for API level 24).

Whatever scrollable container you are using needs to wrapped in a CoordinatorLayout in your layout. In my example I am using a NestedScrollView:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- content -->

    </android.support.v4.widget.NestedScrollView>

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

The CoordinatorLayout allows you to assign a Behavior to its direct child views. In this case we are going to assign a Behavior to the NestedScrollView which is going implement the overscroll bounce effect.

Let's just take a look at the code of the Behavior:

public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior<View> {

    private int mOverScrollY;

    public OverScrollBounceBehavior() {
    }

    public OverScrollBounceBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        mOverScrollY = 0;
        return true;
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        if (dyUnconsumed == 0) {
            return;
        }

        mOverScrollY -= dyUnconsumed;
        final ViewGroup group = (ViewGroup) target;
        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            view.setTranslationY(mOverScrollY);
        }
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        final ViewGroup group = (ViewGroup) target;
        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            ViewCompat.animate(view).translationY(0).start();
        }
    }
}

Explaining what a Behavior is and how they work is beyond the scope of this answer so I am just going to quickly explain what the above code does. The Behavior intercepts all scroll events that happen in the direct children of the CoordinatorLayout. In the onStartNestedScroll() method we return true since we are interested in any scroll events. In onNestedScroll() we look at the dyUnconsumed parameter which tells us how much of the vertical scroll was not consumed by the scrolling container (in other words overscroll) and then translate the children of the scrolling container by that amount. Since we are just getting delta values we need to sum up all of them in the mOverscrollY variable. onStopNestedScroll() is called when the scrolling event stops. This is when we animate all children of the scrolling container back to their original position.

To assign the Behavior to the NestedScrollView we need to use the layout_behavior xml attribute and pass in the full class name of the Behavior we want to use. In my example the above class is in the package com.github.wrdlbrnft.testapp so I have to set com.github.wrdlbrnft.testapp.OverScrollBounceBehavior as value. layout_behavior is a custom attribute of the CoordinatorLayout so we need to prefix it with the correct namespace:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.github.wrdlbrnft.testapp.OverScrollBounceBehavior">

        <!-- content -->

    </android.support.v4.widget.NestedScrollView>

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

Notice the namespace I added on the CoordinatorLayout and the app:layout_behavior attribute I added on the NestedScrollView.

And that is all you have to do! While this answer turned out to be longer than I intended I skipped over some of the basics concering the CoordinatorLayout and Behaviors. So if you are unfamiliar with these or have any other further questions feel free to ask.

like image 130
Xaver Kapeller Avatar answered Sep 19 '22 15:09

Xaver Kapeller


Thanks to Xaver Kapeller, I have written my solution with overriding fling and little additions, using kotlin and androidx

enter image description here

Add coordinator dependency

implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"

Create a new class that extends CoordinatorLayout.Behavior

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat

class OverScrollBehavior(context: Context, attributeSet: AttributeSet)
: CoordinatorLayout.Behavior<View>() {

companion object {
    private const val OVER_SCROLL_AREA = 4
}

private var overScrollY = 0

override fun onStartNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    directTargetChild: View,
    target: View,
    axes: Int,
    type: Int
): Boolean {
    overScrollY = 0
    return true
}

override fun onNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    dxConsumed: Int,
    dyConsumed: Int,
    dxUnconsumed: Int,
    dyUnconsumed: Int,
    type: Int,
    consumed: IntArray
) {
    if (dyUnconsumed == 0) {
        return
    }

    overScrollY -= (dyUnconsumed/OVER_SCROLL_AREA)
    val group = target as ViewGroup
    val count = group.childCount
    for (i in 0 until count) {
        val view = group.getChildAt(i)
        view.translationY = overScrollY.toFloat()
    }
}

override fun onStopNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    type: Int
) {
    // Smooth animate to 0 when the user stops scrolling
    moveToDefPosition(target)
}

override fun onNestedPreFling(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    velocityX: Float,
    velocityY: Float
): Boolean {
    // Scroll view by inertia when current position equals to 0
    if (overScrollY == 0) {
        return false
    }
    // Smooth animate to 0 when user fling view
    moveToDefPosition(target)
    return true
}

private fun moveToDefPosition(target: View) {
    val group = target as ViewGroup
    val count = group.childCount
    for (i in 0 until count) {
        val view = group.getChildAt(i)
        ViewCompat.animate(view)
            .translationY(0f)
            .setInterpolator(AccelerateDecelerateInterpolator())
            .start()
    }
}

}

Create XML file with CoordinatorLayout and NestedScrollView

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior=".OverScrollBehavior">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textAlignment="center"
            android:padding="10dp"
            android:text="@string/Lorem"/>
    </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

And don't forget to add

app:layout_behavior=".OverScrollBehavior" // Or your file name

field to your NestedScrollView XML markup

like image 44
lincollincol Avatar answered Sep 20 '22 15:09

lincollincol


If you want to use library then this Bouncy is the best library which is the best match with your requirement.

like image 32
Rishabh Thakur Avatar answered Sep 19 '22 15:09

Rishabh Thakur