Nicolas Ducom Nicolas Ducom - 7 days ago 4
Android Question

Set Item Alpha relative to center on Horizontal RecyclerView

I'm working on an Android Project as a junior developer and am still learning as I go, I'm having a bit of a design issue and was wondering if somebody could enlighten me.

I have to display items on a horizontal list, I've done so with a RecyclerView, I can only have three items on screen at a time (one centered and two partially visible) and I'm always keeping one in the center thanks to a LinearSnapHelper. The problem I'm having is that the items on the side need to have a lower alpha than the one in the middle (0.5 vs 1). Ideally I would like to make them fade out and in progressively while the user I scrolling but I am completely at a loss as to where I am supposed to do this.

Horrible mockup here

Is there a clean way to do this ?

Thank you very much :)

Answer

If your RecyclerView's background is a solid color, ie default Activity background color or white, a very simple approach would be to overlay the recycler with a semi transparent gradient drawable fading from a ~50% opacity bright color on the sides to a completely transparent one in the middle. This will give the illusion that the items are actually fading as you move them.

However creating complex gradients is not easy in drawable XML: you could just create an horizontal 3 colors gradient

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#55FFFFFF"
        android:centerColor="#00FFFFFF"
        android:endColor="#55FFFFFF"/>
</shape>
<!-- replace FFFFFF with the actual background color
     and adjust the first two hexadecimal digits for the
     overlay transparency (00 is transparent -> FF is opaque) -->

but probably this gradient will cover too much the center element and will be annoying.

In the central part the overlay has to be totally transparent, the gradient should start from outside the space occupied by the central view. This can be accomplished by overlaying two gradients:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:gravity="left"
        android:right="200dp">
        <shape android:shape="rectangle">
            <gradient
                android:type="linear"
                android:startColor="#55FFFFFF"
                android:endColor="#00FFFFFF"/>
        </shape>
    </item>
    <item
        android:left="200dp"
        android:gravity="right">
        <shape android:shape="rectangle">
            <gradient
                android:type="linear"
                android:startColor="#55FFFFFF"
                android:endColor="#AAFFFFFF"/>
        </shape>
    </item>
</layer-list>

Layer list is the only way to create multiple gradients in Android drawables. android:right="200dp" and android:left="200dp" are the distances between each gradient end and the opposite border of the view. In short if you replace 200dp with the space occupied by one side item + the central item, the space at the center will be left transparent as there will be no gradient on it.
Set one of these two gradients drawables in RecyclerView's android:foreground="" attribute in XML and see if it works.

The programmatic approach implies setting an OnScrollListener in RecyclerView or creating a custom LinearLayoutManager which overrides scrollHorizontallyBy(), something like this:

public void updateChildrenAlpha() {
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        float maxDist = /* distance where alpha is min */;
        float right = getDecoratedRight(child);
        float left = getDecoratedLeft(child);
        float childCenter = left + (right - left) / 2; // Get item center position
        float center = getWidth() / 2; // Get RecyclerView's center position
        child.setAlpha((Math.abs(center - childCenter) -  maxDist) / maxDist);
        // Map between 0f and 1f the abs value of the distance
        // between the center of item and center of the RecyclerView
        // and set it as alpha
    }
}

@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    updateChildrenAlpha();
    return super.scrollHorizontallyBy(dx, recycler, state);
}

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    super.onLayoutChildren(recycler, state);
    updateChildrenAlpha();
}