Android Content Placeholder Animation like Facebook using Shimmer



  • Thông thường, chúng ta thường sử dụng spinner khi ứng dụng muốn load data từ network. Thay thế cho việc sử dụng spinner, chúng ta có thể loading screen tốt hơn với Facebook's Shimmer library. Library thêm hiệu ứng Shimmer cho bất cứ custom view mà chúng ta định nghĩa. Bạn có thể chú ý hiệu ứng shimmer ở Facebook trên web và mobile.

    1. Facebook's Shimmer Library

    Để tạo hiệu ứng Shimmer trên bất cứ layout, bạn phải đặt layout của banaj bên trong ShimmerFrameLayout. Để bắt animation, bạn phải call startShimmerAnimation() on ShimmerFrameLayout. Ngay lập tức bạn có nhận thấy hiệu ứng Shimmer trên layout của bạn. Bạn có thể tìm kiếm thêm ở Document trên Shimmer's page.
    Ảnh demo
    Dưới đây là code để làm được hiệu ứng Shimmer. đầu tiên đặt layout của bạn trong 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>
    

    Để start animation, chúng ta gọi function startShimmerAnimation() từ Activity.

    ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);
    shimmerContainer.startShimmerAnimation();
    

    2. Sample JSON

    Để giải thích cho ví dụ demo, tôi sử dụng samplle JSON dưới đây. JSON API

    [{
        "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",
        "thumbnail": "https://api.androidhive.info/images/food/1.jpg",
        "timestamp": "2 min ago"
    }, {
        "id": 2,
        "name": "Grilled Mushroom",
        "description": "Combine butter, dill and garlic salt, brush over mushrooms.",
        "price": 150,
        "chef": "Ravi Tamada",
        "thumbnail": "https://api.androidhive.info/images/food/2.jpg",
        "timestamp": "5 min ago"
    }]
    

    3. Create New Project

    1. Tạo 1 project từ Android Studio: File -> New Project -> Basic Activity từ template
    2. Thêm thư viện Shimmer từ dependency từ build.grade và rebuild
    build.gradle
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:26.1.0'
     
        // Shimmer
        implementation 'com.facebook.shimmer:shimmer:[email protected]'
    }
    
    1. Thêm đoạn code dưới vào file colors và dimens
    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>
    
    1. Tạo layout mới với tên recipe_placeholder_item.xml. Trong file chúng ta định nghĩa cách hiển thị layout
    <?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>
    
    1. Tiếp tục, chúng ta đặt layout đã tạo ở trên vào activity_main.xml.
    activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:shimmer="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        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>
    
    1. Trong MainActivity.java gọi function startShimmerAnimation() trong onResume(). Và pausing animation trong onPause().
    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();
        }
    }
    

    Ngây bây giờ nếu chạy ứng dụng, bạn có thể thấy Shimmer animation bên dưới
    Ảnh demo

    3.1 Loading data từ JSON và hiding đến Shimmer

    1. Trong file 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:[email protected]'
     
        // 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'
    }
    
    1. Tạo file MyApplication.java và extend từ Application. Class này là singleton dùng để khởi tạo Volley.
    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);
            }
        }
    }
    
    1. Đăng ký MyApplication trong AndroidManifest.xml
    AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        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> 
    
    1. Open activity_main.xml và thêm RecyclerView dưới layout ShimmerFrameLayout.
    activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:shimmer="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        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>
    
    1. Tạo class Recipe.java và định nghĩa các biến cần dùng đê parse 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;
       }
    }
    
    1. Tạo layout recipe_list_item.xml. Layout này sẽ hiển thị chính xác item cần hiển thị gồm: image và 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>
    
    1. Tạo Adapter RecipeListAdapter.java cho RecyclerView.
    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();
        }
    }
    
    1. Open MainActivity.java và thay đổi code như bên dưới.

    fetchRecipes() method fetches JSON bằng Volley’s http. Sử dụng GSon để parse JSON.
    JSON được parse và add RecyclerView adapter, List item sẽ được rendered vàShimmerFrameLayout sẽ bị ẩn đi.

    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();
        }
    }
    

    Video Demo

    Source Code

    Nguồn: Viblo


Hãy đăng nhập để trả lời
 

Có vẻ như bạn đã mất kết nối tới LaptrinhX, vui lòng đợi một lúc để chúng tôi thử kết nối lại.