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 transition in three phases at staggered durations". Those being Opacity, Exposure and Saturation:
I'm currently using the Volley NetworkImageView (actually a derived class from this).
I'm sure it's got to be some variant of the answer to this question. I'm just not sure which classes/code to use for both the saturation and animation curves that are described.
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.
See, the excellent answer provided by @DavidCrawford.
BTW: I fixed the linked GitHubProject to support pre-Lollipop devices. (Since API Level 11)
AlphaSatColorMatrixEvaluator.java
import android.animation.TypeEvaluator;
import android.graphics.ColorMatrix;
public class AlphaSatColorMatrixEvaluator implements TypeEvaluator {
private ColorMatrix colorMatrix;
float[] elements = new float[20];
public AlphaSatColorMatrixEvaluator() {
colorMatrix = new ColorMatrix ();
}
public ColorMatrix getColorMatrix() {
return colorMatrix;
}
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// There are 3 phases so we multiply fraction by that amount
float phase = fraction * 3;
// Compute the alpha change over period [0, 2]
float alpha = 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]
final int MaxBlacker = 100;
float blackening = (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.SetSaturation
float invSat = 1 - Math.max(0.2f, fraction);
float R = 0.213f * invSat;
float G = 0.715f * invSat;
float B = 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;
}
}
ImageView imageView = (ImageView)findViewById(R.id.imageView);
final BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.image);
imageView.setImageDrawable(drawable);
AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator ();
final ColorMatrixColorFilter filter = new ColorMatrixColorFilter(evaluator.getColorMatrix());
drawable.setColorFilter(filter);
ObjectAnimator animator = ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawable.setColorFilter (filter);
}
});
animator.setDuration(1500);
animator.start();
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)
private class AnimateColorMatrixColorFilter {
private ColorMatrixColorFilter mFilter;
private ColorMatrix mMatrix;
public AnimateColorMatrixColorFilter(ColorMatrix matrix) {
setColorMatrix(matrix);
}
public ColorMatrixColorFilter getColorFilter() {
return mFilter;
}
public void setColorMatrix(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.
@Override
public void setImageBitmap(Bitmap bm) {
final Drawable drawable = new BitmapDrawable(getContext().getResources(), bm);
setImageDrawable(drawable);
AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator();
final AnimateColorMatrixColorFilter filter = new AnimateColorMatrixColorFilter(evaluator.getColorMatrix());
drawable.setColorFilter(filter.getColorFilter());
ObjectAnimator animator = ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawable.setColorFilter(filter.getColorFilter());
}
});
animator.setDuration(1500);
animator.start();
}
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 | Aa
Xm = multiplicative field
Xa = 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With