Custom Distort EdgeEffect in Android ScrollView

I have wrote an article about customization of scrolling content with overscroll and overfling distance, but what about custom EdgeEffect during overscroll.
Let’s go crazy with idea to distort scrolling content.

Yuriy Skul
4 min readAug 18, 2020

Part 1: scrollView with custom overscroll and overfling behavior.
Part 2: custom Glowing EdgeEffect in Android RecyclerView.
Part 3: current story.

Starter code.
We can create any custom view with scroll behavior using OverScroller or use RecyclerView, NestedScrollView or ScrollView.

I will implement this effect with the help of standard NestedScrollView.
Well, using EdgeEffect.java is not necessary.
So the starter is just a fully copy-pasted NestedScrollView with one direct child — multiline textView.

Custom EdgeEffect.
It can be implemented without EdgeEffect class at all but I will use it as a base code trying to make as less modifications as it is possible.

Let’s create a class DistortEdgeEffect.java which has exactly same code like standard java EdgeEffect.java.

We don't need paint for this effect so let’s remove first line with
public static final BlendMode DEFAULT_BLEND_MODE and its getter/setter, some annotations and local variables: TypedArray and themeColor from constructor.
For paint object inside constructor set mock color: mPaint.setColor(Color.RED);
So the code should compile successfully.

Integrate DistortEdgeEffect into NestedScrollView.

Change type of field variables mEdgeGlowTop and mEdgeGlowBottom from standard to custom DistortEdgeEffect.
Modify onTouchEvent() method of replica NestedScrollView. Instead of using EdgeEffectCompat use directly bottom and top edge variables:

if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex)
/ getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}

Now the code should compile and run successfully.

Circular edge equation.

Before get started with distorting let’s implement custom edge equation I don’t like the way how it is implemented in standard EdgeEffect.

In DistortEdgeEffect define new fields and initialize them from setSize().

Create new method drawWithDistortion(). This method is almost the same as standard one : draw() but has little bit another circular edge
mechanics and displacement behavior. Main idea is to compute center X and Y of circle by three points. X is very easy to compute:

final float centerX = mDisplacement * mFullWidth;

For computing center Y use standard formula inside computeY() method. https://en.wikipedia.org/wiki/Circumscribed_circle.

And call from NestedScrollView.draw() new drawWithDistortion() method for mEdgeGlowTop.

custom edge effect geometry and behavior

Distorting scrolling content.

Probably we can go crazy with opengl using its magic but I want to do it as simple as it is possible. Unfortunately there is no possibility to deform canvas directly. Bitmap has a transform matrix feature but there is no possibility to create matrix with such difficult transformation logic.
Luckily we have canvas.drawBitmapMesh(). So the trick is instead of drawing content to canvas directly — draw it into bitmap and then apply drawBitmapMesh().

Distorting idea is to split bitmap into many vertical rectangles and distort each of them as it is shown on the screen:

distorting the scrolling content

There are lots of articles about it:

So I will not explain in details next lines.

The first half of mDistrotedVertices array has top distorted coordinates of X and Y and second one has bottom coordinates. Top Y coordinates have some distort offset. I compute this offset in computePointY() method. ( Thanks to Pythagoras). Steps count is 40 but event with 20 it looks very smooth. I have changed radius factor from 0.6 to 1 to make distortion to take more place. Instead of meshing directly from bitmap into canvas I draw with distortion into helper canvas at first and then finally use its distorted bitmap to draw with vertical offset into main canvas. I had flickering bug without using helper in-between canvas for “bitmap meshing”

Solution is still draft and is needed to be optimized also DistortEdgeEffect.java has lots of obsolete and unused logic from standard EdgeEffect.java .

Thanks for reading.

Final source code: https://github.com/yuriyskulskiy/DistortEdgeEffect

--

--