

Android Content Placeholder Animation like Facebook using Shimmer
source link: https://www.androidhive.info/2018/01/android-content-placeholder-animation-like-facebook-using-shimmer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android Content Placeholder Animation like Facebook using Shimmer
By Ravi Tamada June 3, 2018 81 CommentsWe normally use spinner loaders when the app wants to load the data from a network call. Instead of using the usual loaders, we can make the loading screen more interesting using Facebook’s Shimmer library. This library adds Shimmer effect on to any custom view that we define. You can notice this shimmer effect in Facebook’s mobile and desktop apps.
This article explains how to use the Shimmer library in your apps with an example of loading a list data from a JSON http call.
1. Facebook’s Shimmer Library
To get shimmer effect on any layout, you have to place your layout inside ShimmerFrameLayout. To start the animation, you have to call startShimmerAnimation() on ShimmerFrameLayout. That’s all, you can immediately notice the shimmer effect on your layout. You can find the additional documentation on the Shimmer’s page.
Below is the code snippet to get the Shimmer effect. First, place your layout inside ShimmerFrameLayout.
<
com.facebook.shimmer.ShimmerFrameLayout
android:id=“@+id/shimmer_view_container”
android:layout_width=“wrap_content”
android:layout_height
=
"wrap_content"
shimmer:duration
=
"1000"
>
<
View
android:layout_width
=
"100dp"
android:layout_height
=
"8dp"
android:background
=
"#dddddd"
/>
</
com.facebook.shimmer.ShimmerFrameLayout
>
To start the animation, call startShimmerAnimation() from your activity.
ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);
shimmerContainer.startShimmerAnimation();
2. Sample JSON
To demonstrate this example, I have created a sample JSON that contains a list of recipes. This endpoint simulates the network delay by adding 2 sec delay before responding the JSON so that the Shimmer effect can be noticed.
https://api.androidhive.info/json/shimmer/menu.php
[{
"id"
: 1,
"name"
:
"Salmon Teriyaki"
,
"description"
:
"Put the ginger and garlic into a bowl and mix with the soy sauce, maple syrup, mirin and a drizzle of olive oil"
,
"price"
: 140,
"chef"
:
"Gordon Ramsay"
,
"timestamp"
:
"2 min ago"
}, {
"id"
: 2,
"name"
:
"Grilled Mushroom"
,
"description"
:
"Combine butter, dill and garlic salt, brush over mushrooms."
,
"price"
: 150,
"chef"
:
"Ravi Tamada"
,
"timestamp"
:
"5 min ago"
}
]
Let’s try the Shimmer library by creating a simple app.
3. Creating New Project
1. Create a new project in Android Studio from File ⇒ New Project and select Basic Activity from templates.
2. Add Shimmer dependency to your build.gradle and rebuild the project.
dependencies {
implementation fileTree(dir:
'libs'
,
include
: [
'*.jar'
])
implementation
'com.android.support:appcompat-v7:26.1.0'
// Shimmer
implementation
'com.facebook.shimmer:shimmer:0.1.0@aar'
}
3. Add the below colors and dimens to respective files.
<!--?xml version="1.0" encoding="utf-8"?-->
<
resources
>
<
color
name
=
"colorPrimary"
>#d91248</
color
>
<
color
name
=
"colorPrimaryDark"
>#d91248</
color
>
<
color
name
=
"colorAccent"
>#3ad23e</
color
>
<
color
name
=
"placeholder_bg"
>#dddddd</
color
>
<
color
name
=
"item_name"
>#0c0c0c</
color
>
<
color
name
=
"description"
>#1a1a1a</
color
>
<
color
name
=
"chef"
>#777</
color
>
<
color
name
=
"timestamp"
>#777</
color
>
</
resources
>
<!--?xml version="1.0" encoding="utf-8"?-->
<
resources
>
<
dimen
name
=
"activity_padding"
>16dp</
dimen
>
<
dimen
name
=
"placeholder_image"
>50dp</
dimen
>
<
dimen
name
=
"placeholder_text_height"
>8dp</
dimen
>
<
dimen
name
=
"activity_padding_horizontal"
>16dp</
dimen
>
<
dimen
name
=
"padding_10"
>10dp</
dimen
>
<
dimen
name
=
"name"
>15dp</
dimen
>
<
dimen
name
=
"chef"
>12dp</
dimen
>
<
dimen
name
=
"timestamp"
>11dp</
dimen
>
<
dimen
name
=
"description"
>15dp</
dimen
>
<
dimen
name
=
"price"
>13dp</
dimen
>
</
resources
>
4. Create a new layout xml file named recipe_placeholder_item.xml. In this file we define the placeholder layout using plain View elements. All the views will be placed similar to actual list items.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:padding
=
"@dimen/activity_padding"
>
<
View
android:id
=
"@+id/thumbnail"
android:layout_width
=
"@dimen/placeholder_image"
android:layout_height
=
"@dimen/placeholder_image"
android:layout_marginRight
=
"@dimen/activity_padding"
android:background
=
"@color/placeholder_bg"
/>
<
View
android:id
=
"@+id/name"
android:layout_width
=
"150dp"
android:layout_height
=
"10dp"
android:layout_marginBottom
=
"10dp"
android:layout_toRightOf
=
"@id/thumbnail"
android:background
=
"@color/placeholder_bg"
/>
<
View
android:layout_width
=
"100dp"
android:layout_height
=
"@dimen/placeholder_text_height"
android:layout_below
=
"@id/name"
android:layout_toRightOf
=
"@id/thumbnail"
android:background
=
"@color/placeholder_bg"
/>
<
LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:layout_below
=
"@id/thumbnail"
android:layout_marginBottom
=
"40dp"
android:layout_marginTop
=
"20dp"
android:orientation
=
"vertical"
>
<
View
android:layout_width
=
"match_parent"
android:layout_height
=
"@dimen/placeholder_text_height"
android:layout_marginRight
=
"100dp"
android:background
=
"@color/placeholder_bg"
/>
<
View
android:layout_width
=
"match_parent"
android:layout_height
=
"@dimen/placeholder_text_height"
android:layout_marginRight
=
"50dp"
android:layout_marginTop
=
"10dp"
android:background
=
"@color/placeholder_bg"
/>
<
View
android:layout_width
=
"match_parent"
android:layout_height
=
"@dimen/placeholder_text_height"
android:layout_marginRight
=
"160dp"
android:layout_marginTop
=
"10dp"
android:background
=
"@color/placeholder_bg"
/>
</
LinearLayout
>
</
RelativeLayout
>
5. As the placeholder view is ready, let’s add it to our main activity layout. Open the layout file of your main activity i.e activity_main.xml and include the placeholder layout. We are including the placeholder layout three times to make it appear as list.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
android.support.constraint.ConstraintLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:background
=
"@android:color/white"
tools:context
=
"info.androidhive.shimmer.MainActivity"
>
<
com.facebook.shimmer.ShimmerFrameLayout
android:id
=
"@+id/shimmer_view_container"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center"
android:orientation
=
"vertical"
shimmer:duration
=
"800"
>
<!-- Adding 3 rows of placeholders -->
<
LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"vertical"
>
<
include
layout
=
"@layout/recipe_placeholder_item"
/>
<
include
layout
=
"@layout/recipe_placeholder_item"
/>
<
include
layout
=
"@layout/recipe_placeholder_item"
/>
</
LinearLayout
>
</
com.facebook.shimmer.ShimmerFrameLayout
>
</
android.support.constraint.ConstraintLayout
>
6. Open MainActivity.java and start the Shimmer animation by calling startShimmerAnimation() method in onResume(). We are also pausing the animation in onPause() when the activity is paused.
import
com.facebook.shimmer.ShimmerFrameLayout;
public
class
MainActivity
extends
AppCompatActivity {
private
ShimmerFrameLayout mShimmerViewContainer;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mShimmerViewContainer = findViewById(R.id.shimmer_view_container);
}
@Override
public
void
onResume() {
super
.onResume();
mShimmerViewContainer.startShimmerAnimation();
}
@Override
public
void
onPause() {
mShimmerViewContainer.stopShimmerAnimation();
super
.onPause();
}
}
Now if you run the app, you can see the below Shimmer animation.
3.1 Loading feed from JSON and hiding the Shimmer
Now as our Shimmer loader is ready, let’s see how to load the JSON feed in RecyclerView and hide the shimmer loader once the list is rendered. By following the remaining part of the article, you will understand how to implement the Shimmer effect in a real world app.
7. Open the build.gradle and add RecyclerView, Glide and Volley dependencies.
dependencies {
implementation fileTree(dir:
'libs'
,
include
: [
'*.jar'
])
implementation
'com.android.support:appcompat-v7:26.1.0'
// ...
// Shimmer
implementation
'com.facebook.shimmer:shimmer:0.1.0@aar'
// RecyclerView
implementation
'com.android.support:recyclerview-v7:26.1.0'
// glide image library
implementation
'com.github.bumptech.glide:glide:3.7.0'
// volley http library
implementation
'com.android.volley:volley:1.0.0'
implementation
'com.google.code.gson:gson:2.6.2'
}
8. Create a class named MyApplication.java and extend the class from Application. This is a singleton class in which the Volley library will be initiated.
package
info.androidhive.shimmer;
/**
* Created by ravi on 18/01/18.
*/
import
android.app.Application;
import
android.text.TextUtils;
import
com.android.volley.Request;
import
com.android.volley.RequestQueue;
import
com.android.volley.toolbox.Volley;
public
class
MyApplication
extends
Application {
public
static
final
String TAG = MyApplication.
class
.getSimpleName();
private
RequestQueue mRequestQueue;
private
static
MyApplication mInstance;
@Override
public
void
onCreate() {
super
.onCreate();
mInstance =
this
;
}
public
static
synchronized
MyApplication getInstance() {
return
mInstance;
}
public
RequestQueue getRequestQueue() {
if
(mRequestQueue ==
null
) {
mRequestQueue = Volley.newRequestQueue(getApplicationContext());
}
return
mRequestQueue;
}
public
<T>
void
addToRequestQueue(Request<T> req, String tag) {
// set the default tag if tag is empty
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
getRequestQueue().add(req);
}
public
<T>
void
addToRequestQueue(Request<T> req) {
req.setTag(TAG);
getRequestQueue().add(req);
}
public
void
cancelPendingRequests(Object tag) {
if
(mRequestQueue !=
null
) {
mRequestQueue.cancelAll(tag);
}
}
}
9. Open AndroidManifest.xml and add the MyApplication class to <application> tag. We also need INTERNET permission as we are going to make http calls.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
package
=
"info.androidhive.shimmer"
>
<
uses-permission
android:name
=
"android.permission.INTERNET"
/>
<!-- add .MyApplication class -->
<
application
android:allowBackup
=
"true"
android:icon
=
"@mipmap/ic_launcher"
android:label
=
"@string/app_name"
android:roundIcon
=
"@mipmap/ic_launcher_round"
android:supportsRtl
=
"true"
android:name
=
".MyApplication"
android:theme
=
"@style/AppTheme"
>
<
activity
android:name
=
".MainActivity"
>
<
intent-filter
>
<
action
android:name
=
"android.intent.action.MAIN"
/>
<
category
android:name
=
"android.intent.category.LAUNCHER"
/>
</
intent-filter
>
</
activity
>
</
application
>
</
manifest
>
10. Open activity_main.xml and add RecyclerView widget below the ShimmerFrameLayout.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
android.support.constraint.ConstraintLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:background
=
"@android:color/white"
tools:context
=
"info.androidhive.shimmer.MainActivity"
>
<
com.facebook.shimmer.ShimmerFrameLayout
android:id
=
"@+id/shimmer_view_container"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center"
android:orientation
=
"vertical"
shimmer:duration
=
"800"
>
<!-- Adding 3 rows of placeholders -->
<
LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"vertical"
>
<
include
layout
=
"@layout/layout_placeholder_row"
/>
<
include
layout
=
"@layout/layout_placeholder_row"
/>
<
include
layout
=
"@layout/layout_placeholder_row"
/>
</
LinearLayout
>
</
com.facebook.shimmer.ShimmerFrameLayout
>
<
android.support.v7.widget.RecyclerView
android:id
=
"@+id/recycler_view"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:scrollbars
=
"vertical"
/>
</
android.support.constraint.ConstraintLayout
>
11. Create a new class named Recipe.java and define the following variables. This is a POJO class used to serialize the JSON.
package
info.androidhive.shimmer;
/**
* Created by ravi on 18/01/18.
*/
public
class
Recipe {
int
id;
String name;
String description;
double
price;
String thumbnail;
String chef;
String timestamp;
public
Recipe() {
}
public
int
getId() {
return
id;
}
public
String getName() {
return
name;
}
public
String getDescription() {
return
description;
}
public
double
getPrice() {
return
price;
}
public
String getThumbnail() {
return
thumbnail;
}
public
String getChef() {
return
chef;
}
public
String getTimestamp() {
return
timestamp;
}
}
12. Create a new xml layout named recipe_list_item.xml. This layout holds the actual list row with a thumbnail image and few TextViews.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:background
=
"@android:color/white"
android:clickable
=
"true"
android:foreground
=
"?attr/selectableItemBackground"
android:padding
=
"@dimen/activity_padding_horizontal"
>
<
ImageView
android:id
=
"@+id/thumbnail"
android:layout_width
=
"@dimen/placeholder_image"
android:layout_height
=
"@dimen/placeholder_image"
android:layout_marginRight
=
"@dimen/padding_10"
android:scaleType
=
"centerCrop"
/>
<
TextView
android:id
=
"@+id/name"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_toRightOf
=
"@id/thumbnail"
android:ellipsize
=
"end"
android:fontFamily
=
"sans-serif-medium"
android:maxLines
=
"1"
android:textColor
=
"@color/item_name"
android:textSize
=
"@dimen/name"
/>
<
TextView
android:id
=
"@+id/chef"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_below
=
"@id/name"
android:layout_toRightOf
=
"@id/thumbnail"
android:maxLines
=
"1"
android:textColor
=
"@color/chef"
android:textSize
=
"@dimen/chef"
/>
<
TextView
android:id
=
"@+id/timestamp"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_below
=
"@id/chef"
android:layout_toRightOf
=
"@id/thumbnail"
android:maxLines
=
"1"
android:text
=
"2 min ago"
android:textColor
=
"@color/timestamp"
android:textSize
=
"@dimen/timestamp"
/>
<
TextView
android:id
=
"@+id/description"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_below
=
"@id/thumbnail"
android:layout_marginTop
=
"@dimen/activity_padding_horizontal"
android:ellipsize
=
"end"
android:maxLines
=
"3"
android:textColor
=
"@color/description"
android:textSize
=
"@dimen/description"
/>
<
TextView
android:id
=
"@+id/price"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_below
=
"@id/description"
android:layout_marginTop
=
"@dimen/padding_10"
android:textColor
=
"@color/colorPrimary"
android:textSize
=
"@dimen/price"
android:textStyle
=
"bold"
/>
</
RelativeLayout
>
13. We need another class to write the necessary adapter required for RecyclerView. Create a class named RecipeListAdapter.java and add below code.
package
info.androidhive.shimmer;
/**
* Created by ravi on 18/01/18.
*/
import
android.content.Context;
import
android.support.v7.widget.RecyclerView;
import
android.view.LayoutInflater;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.ImageView;
import
android.widget.TextView;
import
com.bumptech.glide.Glide;
import
java.util.List;
public
class
RecipeListAdapter
extends
RecyclerView.Adapter<RecipeListAdapter.MyViewHolder> {
private
Context context;
private
List<Recipe> cartList;
public
class
MyViewHolder
extends
RecyclerView.ViewHolder {
public
TextView name, description, price, chef, timestamp;
public
ImageView thumbnail;
public
MyViewHolder(View view) {
super
(view);
name = view.findViewById(R.id.name);
chef = view.findViewById(R.id.chef);
description = view.findViewById(R.id.description);
price = view.findViewById(R.id.price);
thumbnail = view.findViewById(R.id.thumbnail);
timestamp = view.findViewById(R.id.timestamp);
}
}
public
RecipeListAdapter(Context context, List<Recipe> cartList) {
this
.context = context;
this
.cartList = cartList;
}
@Override
public
MyViewHolder onCreateViewHolder(ViewGroup parent,
int
viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recipe_list_item, parent,
false
);
return
new
MyViewHolder(itemView);
}
@Override
public
void
onBindViewHolder(MyViewHolder holder,
final
int
position) {
final
Recipe recipe = cartList.get(position);
holder.name.setText(recipe.getName());
holder.chef.setText(
"By "
+ recipe.getChef());
holder.description.setText(recipe.getDescription());
holder.price.setText(
"Price: ₹"
+ recipe.getPrice());
holder.timestamp.setText(recipe.getTimestamp());
Glide.with(context)
.load(recipe.getThumbnail())
.into(holder.thumbnail);
}
// recipe
@Override
public
int
getItemCount() {
return
cartList.size();
}
}
14. Now we have everything in place. Open MainActivity.java and modify the code as shown below.
> fetchRecipes() method fetches the JSON by making Volley’s http call. The JSON is parsed using Gson serializer.
> Once the JSON is parsed and added to RecyclerView adapter, the list is rendered and ShimmerFrameLayout is hidden making the actual list visible on the screen.
package
info.androidhive.shimmer;
import
android.support.v7.app.AppCompatActivity;
import
android.os.Bundle;
import
android.support.v7.widget.DefaultItemAnimator;
import
android.support.v7.widget.LinearLayoutManager;
import
android.support.v7.widget.RecyclerView;
import
android.util.Log;
import
android.view.View;
import
android.widget.Toast;
import
com.android.volley.Response;
import
com.android.volley.VolleyError;
import
com.android.volley.toolbox.JsonArrayRequest;
import
com.facebook.shimmer.ShimmerFrameLayout;
import
com.google.gson.Gson;
import
com.google.gson.reflect.TypeToken;
import
org.json.JSONArray;
import
java.util.ArrayList;
import
java.util.List;
public
class
MainActivity
extends
AppCompatActivity {
private
static
final
String TAG = MainActivity.
class
.getSimpleName();
private
RecyclerView recyclerView;
private
List<Recipe> cartList;
private
RecipeListAdapter mAdapter;
private
ShimmerFrameLayout mShimmerViewContainer;
// URL to fetch menu json
// this endpoint takes 2 sec before giving the response to add
// some delay to test the Shimmer effect
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mShimmerViewContainer = findViewById(R.id.shimmer_view_container);
recyclerView = findViewById(R.id.recycler_view);
cartList =
new
ArrayList<>();
mAdapter =
new
RecipeListAdapter(
this
, cartList);
RecyclerView.LayoutManager mLayoutManager =
new
LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(
new
DefaultItemAnimator());
recyclerView.addItemDecoration(
new
MyDividerItemDecoration(
this
, LinearLayoutManager.VERTICAL,
16
));
recyclerView.setAdapter(mAdapter);
// making http call and fetching menu json
fetchRecipes();
}
/**
* method make volley network call and parses json
*/
private
void
fetchRecipes() {
JsonArrayRequest request =
new
JsonArrayRequest(URL,
new
Response.Listener<JSONArray>() {
@Override
public
void
onResponse(JSONArray response) {
if
(response ==
null
) {
Toast.makeText(getApplicationContext(),
"Couldn't fetch the menu! Pleas try again."
, Toast.LENGTH_LONG).show();
return
;
}
List<Recipe> recipes =
new
Gson().fromJson(response.toString(),
new
TypeToken<List<Recipe>>() {
}.getType());
// adding recipes to cart list
cartList.clear();
cartList.addAll(recipes);
// refreshing recycler view
mAdapter.notifyDataSetChanged();
// stop animating Shimmer and hide the layout
mShimmerViewContainer.stopShimmerAnimation();
mShimmerViewContainer.setVisibility(View.GONE);
}
},
new
Response.ErrorListener() {
@Override
public
void
onErrorResponse(VolleyError error) {
// error in getting json
Log.e(TAG,
"Error: "
+ error.getMessage());
Toast.makeText(getApplicationContext(),
"Error: "
+ error.getMessage(), Toast.LENGTH_SHORT).show();
}
});
MyApplication.getInstance().addToRequestQueue(request);
}
@Override
public
void
onResume() {
super
.onResume();
mShimmerViewContainer.startShimmerAnimation();
}
@Override
public
void
onPause() {
mShimmerViewContainer.stopShimmerAnimation();
super
.onPause();
}
}

I hope this article is pretty simple and explained very well about Shimmer library. If you have any queries, do post them in comments section below.
Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: [email protected]
Recommend
-
63
README.md vue-content-loader
-
66
README.md react-shimmer A powerful, customisable, <img> component that simulates a shimmer eff...
-
41
README.md
-
40
A powerful, customisable, <img> component that simulates a ...
-
35
README.md
-
5
Build Real-World React Native App #4 : Content Placeholder
-
8
Chip Card ATM ‘Shimmer’ Found in Mexico – Krebs on SecurityFraud experts in Mexico have discovered an unusual ATM skimming device that can be inserted into the mouth of the cash machine’s card acceptance slot and used to read data directly of...
-
6
Create a Shimmer Loading Effect in Flutter with Demo AppLoaded: 0.78%00:07Remaining Time -23:55Create a Shimmer Loading Effect in Flutter...
-
10
1. UWP 的 Text Shimmer 动画# 在 UWP 的 Windows Composition Samples 中有一个 Text Shimmer 动画,它用于展示如何使用 Co...
-
4
UI ContentThe best place to find professional placeholder textTransform your design mockups with Lipsum alternative, the perfect solution for UI designers looking to add engaging...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK