Elastic ScrollView

Ayush P Gupta
Jul 23, 2017 · 3 min read

Recently i was looking to build up something like this, a elastic scrollview to actually show a layout behind it.

I didn’t find a library for this, so i decided to code it myself.
First of all, one should be quite familiar with Android Touch Framework especially the touch events such as onTouchEvent(), onInterceptTouchEvent(), if you are not familiar then checkout the link at the bottom of the page.

Here’s the xml of this layout :

<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bye.hey.coordinator.MainActivity"
>

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




<com.bye.hey.coordinator.CustomLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#32d541"
android:paddingTop="8dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>

<LinearLayout
android:id="@+id/aa"
android:layout_width="match_parent"
android:layout_height="164dp"
android:layout_marginTop="8dp"
android:background="@color/colorAccent"
android:orientation="horizontal"
android:scaleX="0.7"
android:scaleY="0.7"
>

<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_vertical"
android:layout_margin="16dp"
android:background="#f4f4f4"
/>

<View..../>

<View..../>

</LinearLayout>

<ScrollView
android:id="@+id/sss"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>

<View
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginBottom="8dp"
android:background="@color/colorPrimaryDark"
/>

<View..../>

<View..../>

</LinearLayout>
</ScrollView>
</com.bye.hey.coordinator.CustomLayout>
</android.support.design.widget.CoordinatorLayout>

I have a CustomLayout which is a FrameLayout having two children one is the back view (pink LinearLayout) and other is a ScrollView having blue colored children.
To implement such design i should actually drag scrollview when it reaches top, or is at top, to show and animate the back view.

To achieve this i have to pass the touch event back to parent of scrollview which is CustomLayout when it reaches top, so that i could handle those touch events to drag scrollview downwards.

For this, i override the onInterceptTouchEvent() in CustomLayout(which is extended FrameLayout in this case).

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
yPrec = ev.getY();
dY = scrollview.getY() - ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
final float dy = ev.getY() - yPrec;
if (dy < mTouchSlop) {
Log.d(TAG, "onInterceptTouchEvent: scrolling down");
} else {
Log.d(TAG, "onInterceptTouchEvent: scrolling up");
if (scrollview.getScrollY() == 0) {
Log.d(TAG, "onInterceptTouchEvent: scrollview is
at the top"
);
return true;
}
}
break;
}

return super.onInterceptTouchEvent(ev);
}

By looking at respective logs you can see which condition is being used to achieve what. Here we are checking whether scrollview is actually scrolling when user is dragging his finger. getScrollY() gives us whether we are at the top or not in a scrollview.
After determining necessary conditions we now return true in this onInterceptTouchMethod() because only then onTouchEvent() will be called.

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: called");


switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent: action_move");
dragView(event);
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent: action_up");
reboundView();
break;
}
return super.onTouchEvent(event);

}

In ACTION_MOVE method we should drag scrollview based on user touch position. In ACTION_UP method we should rebound scrollview back to its original position.

private void dragView(MotionEvent event) {
if ((event.getRawY() + dY) < 400)
scrollview.setY(event.getRawY() + dY);

if ((event.getRawY() + dY) > 0 && (event.getRawY() + dY) < 400)
{
backview.setScaleX((float) (((0.3 / 400.0) *
(event.getRawY() + dY)) + 0.7));
backview.setScaleY((float) (((0.3 / 400.0) *
(event.getRawY() + dY)) + 0.7));
}

}

Note — I am also scaling the back view from 0.7f to 1.0f.

In reboundView() method we are actually dragging back the scrollview when user lifts up his finger. All the animations are played in this method.

private void reboundView() {
Log.d(TAG, "reboundView: " + scrollview.getY());
ValueAnimator valueAnimator = ValueAnimator.ofFloat(scrollview.getY(), 0);
valueAnimator.setDuration(450);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
backview.setScaleX(0.7f);
backview.setScaleY(0.7f);
super.onAnimationEnd(animation);
}

@Override
public void onAnimationStart(Animator animation) {
isAnimating = true;
super.onAnimationStart(animation);
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
scrollview.setY((Float) valueAnimator.getAnimatedValue());
float a = (float) valueAnimator.getAnimatedValue();
backview.setScaleX((float) (((0.3 / 400.0) * (a)) + 0.7));
backview.setScaleY((float) (((0.3 / 400.0) * (a)) + 0.7));
}
});
valueAnimator.start();
}

By the way if want to get access children in this CustomLayout you could use this method:

@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
if (child.getId() == R.id.sss) {
scrollview = ((ScrollView) child);

} else if (child.getId() == R.id.aa) {
backview = ((LinearLayout) child);
}
}

Hence, this is basically a rough workout so as to achieve the desired UI & UX.
This Sample Code is available on git:
https://github.com/apgapg/ElasticScrollView

To read Android Touch Framework:
http://codetheory.in/understanding-android-input-touch-events/

Thanks! Any feedback is welcome.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade