Skip to content Skip to sidebar Skip to footer

How To Implement "loading Images" Pattern (opacity, Exposure And Saturation) From Google's New Material Design Guidelines

Has anyone looked into implementing the Loading images pattern from Google's latest Material Design guide. It's a recommended way that 'illustrations and photographs may load and t

Solution 1:

Thanks to @mttmllns! Previous Answer.

Since the previous answer shows an example written in C# and I was curious, I ported it to java. Complete GitHub Example

It outlines a 3-steps process where a combination of opacity, contrast/luminosity and saturation is used in concert to help salvage our poor users eyesight.

For a detailed explanation read this article.

EDIT:

See, the excellent answer provided by @DavidCrawford.

BTW: I fixed the linked GitHubProject to support pre-Lollipop devices. (Since API Level 11)

The Code

AlphaSatColorMatrixEvaluator.java

import android.animation.TypeEvaluator;
import android.graphics.ColorMatrix;

publicclassAlphaSatColorMatrixEvaluatorimplementsTypeEvaluator {
    private ColorMatrix colorMatrix;
    float[] elements = newfloat[20];

    publicAlphaSatColorMatrixEvaluator() {
        colorMatrix = newColorMatrix ();
    }

    public ColorMatrix getColorMatrix() {
        return colorMatrix;
    }

    @Overridepublic Object evaluate(float fraction, Object startValue, Object endValue) {
        // There are 3 phases so we multiply fraction by that amountfloatphase= fraction * 3;

        // Compute the alpha change over period [0, 2]floatalpha= Math.min(phase, 2f) / 2f;
        // elements [19] = (float)Math.round(alpha * 255);
        elements [18] = alpha;

        // We substract to make the picture look darker, it will automatically clamp// This is spread over period [0, 2.5]finalintMaxBlacker=100;
        floatblackening= (float)Math.round((1 - Math.min(phase, 2.5f) / 2.5f) * MaxBlacker);
        elements [4] = elements [9] = elements [14] = -blackening;

        // Finally we desaturate over [0, 3], taken from ColorMatrix.SetSaturationfloatinvSat=1 - Math.max(0.2f, fraction);
        floatR=0.213f * invSat;
        floatG=0.715f * invSat;
        floatB=0.072f * invSat;

        elements[0] = R + fraction; elements[1] = G;            elements[2] = B;
        elements[5] = R;            elements[6] = G + fraction; elements[7] = B;
        elements[10] = R;           elements[11] = G;           elements[12] = B + fraction;

        colorMatrix.set(elements);
        return colorMatrix;
    }
}

Here is how you can set it up:

ImageViewimageView= (ImageView)findViewById(R.id.imageView);
finalBitmapDrawabledrawable= (BitmapDrawable) getResources().getDrawable(R.drawable.image);
imageView.setImageDrawable(drawable);
AlphaSatColorMatrixEvaluatorevaluator=newAlphaSatColorMatrixEvaluator ();
finalColorMatrixColorFilterfilter=newColorMatrixColorFilter(evaluator.getColorMatrix());
drawable.setColorFilter(filter);

ObjectAnimatoranimator= ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());

animator.addUpdateListener( newValueAnimator.AnimatorUpdateListener() {
    @OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
        drawable.setColorFilter (filter);
    }
});
animator.setDuration(1500);
animator.start();

And here is the result:

enter image description here

Solution 2:

Please note that this answer, as it stands, works for Lollipop only. The reason for this is because the colorMatrix property is not available to animate on the ColorMatrixColorFilter class (it doesn't provide getColorMatrix and setColorMatrix methods). To see this in action, try the code, in logcat output you should see a warning message like this:

Method setColorMatrix() with type class android.graphics.ColorMatrix not found on target class class android.graphics.ColorMatrixColorFilter

That being said, I was able to get this to work on older android versions (pre-Lollipop) by creating the following class (not the best name, I know)

privateclassAnimateColorMatrixColorFilter {
    private ColorMatrixColorFilter mFilter;
    private ColorMatrix mMatrix;

    publicAnimateColorMatrixColorFilter(ColorMatrix matrix) {
        setColorMatrix(matrix);
    }

    public ColorMatrixColorFilter getColorFilter() {
        return mFilter;
    }

    publicvoidsetColorMatrix(ColorMatrix matrix) {
        mMatrix = matrix;
        mFilter = new ColorMatrixColorFilter(matrix);
    }

    public ColorMatrix getColorMatrix() {
        return mMatrix;
    }
}

Then, the setup code would look something like the following. Note that I have this "setup" in a derived class from ImageView and so I'm doing this in the overriden method setImageBitmap.

@OverridepublicvoidsetImageBitmap(Bitmap bm) {
    finalDrawabledrawable=newBitmapDrawable(getContext().getResources(), bm);
    setImageDrawable(drawable);

    AlphaSatColorMatrixEvaluatorevaluator=newAlphaSatColorMatrixEvaluator();
    finalAnimateColorMatrixColorFilterfilter=newAnimateColorMatrixColorFilter(evaluator.getColorMatrix());
    drawable.setColorFilter(filter.getColorFilter());

    ObjectAnimatoranimator= ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());

    animator.addUpdateListener( newValueAnimator.AnimatorUpdateListener() {
        @OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
            drawable.setColorFilter(filter.getColorFilter());
        }
    });
    animator.setDuration(1500);
    animator.start();
}

Solution 3:

Following up on rnrneverdies's excellent answer, I'd like to offer a small fix to this animation logic.

My problem with this implementation is when it comes to png images with transparency (for example, circular images, or custom shapes). For these images, the colour filter will draw the transparency of the image as black, rather than just leaving them transparent.

The problem is with this line:

elements [19] = (float)Math.round(alpha * 255);

What's happening here is that the colour matrix is telling the bitmap that the alpha value of each pixels is equal to the current phase of the alpha. This is obviously not perfect, since pixels which were already transparent will lose their transparency and appear as black.

To fix this, instead of applying the alpha of the "additive" alpha field in the colour matrix, apply it on the "multiplicative" field:

Rm | 0  | 0  | 0  | Ra
0  | Gm | 0  | 0  | Ga
0  | 0  | Bm | 0  | Ba
0  | 0  | 0  | Am | AaXm= multiplicative fieldXa= additive field

So instead of applying the alpha value on the "Aa" field (elements[19]), apply it on the "Am" field (elements[18]), and use the 0-1 value rather than the 0-255 value:

//elements [19] = (float)Math.round(alpha * 255);
elements [18] = alpha;

Now the transition will multiply the original alpha value of the bitmap with the alpha phase of the animation and not force an alpha value when there shouldn't be one.

Hope this helps

Solution 4:

Was just wondering this same thing. I found a blog post detailing how to go about it with an example written in Xamarin:

http://blog.neteril.org/blog/2014/11/23/android-material-image-loading/

The gist for someone writing in Java: use ColorMatrix and ColorMatrixColorFilter.

The post also mentions an important optimization: using the Lollipop hidden setColorMatrix() API to avoid GC and GPU churn.

Have you seen it in use in any Google apps yet? If you end up implementing it I'd love to see your source.

Post a Comment for "How To Implement "loading Images" Pattern (opacity, Exposure And Saturation) From Google's New Material Design Guidelines"