Android: Shimmer effect on custom view

Andy Wang
4 min readJan 23, 2017

--

Introduction

Recently I had a chance to make a custom view for a list loading state. We want something specific for list loading and a bit more exciting than the Android default spin indicator. So we find this shimmer animation on some skeleton UI element, like what the Facebook Android app does when loading your HOME screen.

There is already an open source Android library on Github does a similar thing, i.e. shimmer-android. It will apply shimmer animation to any view you added to the ShimmerFrameLayout.

The example code used in this article could be found here.

Why Custom View?

If you are not familiar with Android Custom View yet, I suggest to reading this article. I choose to do a custom view because of better performance, simple view hierarchy and relatively simple graphic to draws.

Layers

To take this view apart, it consists 3 layers.

Background

Choose some non-transparent color, so that the list skeleton has a base background color. i.e. #AAAAAA with 5% alpha.

Shimmer shader

A linear gradient shader with the edge colors same as the background color. Center color could have bigger alpha value. i.e. #AAAAAA with 20% alpha.

List skeleton

Note that the background is transparent instead of white shown here. The rectangle shapes have the base background color. #AAAAAA with 5% alpha

Efficient drawing

In Android, the View.onDraw method is what all drawing happens. This method will be called multiple times on Android UI thread. Moreover, the Android system will drop frames if the animation drawing cannot keep up with the Vsync. So there are a few tips to keep your custom view more efficient.

  • Absolutely no memory allocation in onDraw. i.e. create any new object instance
  • Do as much as calculations/logic outside of onDraw. i.e. update values related to view size in onSizeChanged
  • Pre-draw any static graphic into a bitmap. So you only draw the bitmap instead of multiple drawings. i.e. the list skeleton

The shimmer view

Let’s started with prepare a static list skeleton bitmap and a gradient shader.

List skeleton

  • Draw a list item skeleton. Note that the bitmap only needs alpha value.
// we only need Alpha value in this bitmap
Bitmap.Config conf = Bitmap.Config.ALPHA_8;
Bitmap item = Bitmap.createBitmap(w, h, conf);

Canvas canvas = new Canvas(item);
// fill canvas with non-transparent color
canvas.drawColor(Color.argb(255, 0, 0, 0));

Paint itemPaint = new Paint();
itemPaint.setAntiAlias(true);
itemPaint.setColor(Color.argb(0, 0, 0, 0));
itemPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

// draw avatar
RectF rectF = new RectF(vSpacing, hSpacing, vSpacing + imageSize, hSpacing + imageSize);
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint);
//draw the reset of the elements...
  • Repeat the list item pattern base on the view height.
// create a new bitmap
Bitmap listItemPattern = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

// draw list items into the bitmap
Canvas canvas = new Canvas(listItemPattern);
Bitmap item = getItemBitmap(w);
int top = 0;
do {
canvas.drawBitmap(item, 0, top, paint);
top = top + item.getHeight();
} while(top < canvas.getHeight());

// only fill the rectangles with the background color
canvas.drawColor(ITEM_PATTERN_BG_COLOR, PorterDuff.Mode.SRC_IN);

Gradient shader

Create a new Paint instance and set LinearGradient on it.

Paint shaderPaint = new Paint();
shaderPaint.setAntiAlias(true);
int[] shaderColors = new int[]{EDGE_COLOR, CENTER_COLOR, EDGE_COLOR};
LinearGradient shader = new LinearGradient(0f, 0f, width, 0f,
shaderColors, new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP);
shaderPaint.setShader(shader);

onDraw

Now we have the pre-draw list skeleton bitmap and the gradient shader prepared. We can draw each layer in the onDraw method.

canvas.drawColor(EDGE_COLOR);// draw gradient background
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), shaderPaint);
// draw list item pattern
canvas.drawBitmap(listItemPattern, 0, 0, paint);

Animation

Now we have a static list shimmer view, we can start applying a left to right animation on the gradient shader.

  • create a simple ValueAnimator
ValueAnimator animator = ValueAnimator.ofFloat(-1f, 1f);
animator.setDuration(ANIMATION_DURATION);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(animatorUpdateListener);
  • update the left position of gradient shader on animation update callback
animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener {    @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if(isAttachedToWindow()) {
float f = (float) valueAnimator.getAnimatedValue();
updateShader(getWidth(), f);
invalidate();
} else {
animator.cancel();
}
}
}private void updateShader(float w, float f) {
float left = w * f;
LinearGradient shader = new LinearGradient(left, 0f, left + w, 0f, shaderColors, new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP);
shaderPaint.setShader(shader);
}
  • start animation onVisibilityChanged
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
switch (visibility) {
case VISIBLE:
animator.start();
break;
case INVISIBLE:
case GONE:
animator.cancel();
break;
}
}

Conclusion

Android Custom View is powerful, especially combining with animators. We could build any cool UI effects with them. However, implementing a custom view is more time consuming than using standard Android Widgets. Moreover, before choose to do a custom view, we should really understand how UI rendering on Android works and following the best practice to make sure the custom view is efficient.

Thank you

Andy Wang, Software Engineer

Linkedin

--

--