13

Android Content Placeholder Animation like Facebook using Shimmer

 4 years ago
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.
neoserver,ios ssh client

Material Design

Android Content Placeholder Animation like Facebook using Shimmer

By Ravi Tamada June 3, 2018 81 Comments

We 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.

VIDEO DEMO

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.

build.gradle
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.

colors.xml
<!--?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>
dimens.xml
<!--?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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
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.

activity_main.xml
<?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.

MainActivity.java
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.

build.gradle
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.

MyApplication.java
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.

AndroidManifest.xml
<?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.

activity_main.xml
<?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.

Recipe.java
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.

recipe_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
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.

RecipeListAdapter.java
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.

MainActivity.java
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
private static final String URL = "https://api.androidhive.info/json/shimmer/menu.php";
@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();
}
}
ic_play.png

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK