Thay vì sử dụng các bộ tải thông thường, thì lần này tôi hướng dẫn bạn làm cho màn hình tải thú vị hơn bằng cách sử dụng thư viện Shimmer của Facebook. Thư viện này thêm hiệu ứng Shimmer vào bất kỳ chế độ xem tùy chỉnh nào mà chúng ta xác định. Bạn có thể nhận thấy hiệu ứng này trong ứng dụng trên điện thoại di động và máy tính trên Facebook.

Bài viết này giải thích cách sử dụng thư viện Shimmer trong ứng dụng của bạn với một ví dụ về tải dữ liệu danh sách từ cuộc gọi http JSON.

1. Thư viện Facebook’s Shimmer

Để có được hiệu ứng này trên bất kỳ layout nào, bạn phải đặt layout bên trong ShimmerFrameLayout. Để sử dụng animation này, bạn phải gọi startShimmerAnimation () trên ShimmerFrameLayout. Đó là tất cả, bạn có thể ngay lập tức nhận thấy hiệu ứng shimmer về layout của bạn. Bạn có thể tìm thấy tài liệu bổ sung trên trang của Shimmer.

Dưới đây là đoạn mã để có được hiệu ứng Shimmer. Đầu tiên, đặt bố cục của bạn 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>

Để bắt đầu hoạt hình, hãy gọi startShimmerAnimation () từ activity của bạn.

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

2. Dữ liệu Json

Để chạy thử demo này, đã có một mẫu JSON có chứa một danh sách các công thức nấu ăn. Điểm cuối này mô phỏng sự chậm trễ của mạng bằng cách thêm trễ 2 giây trước khi trả lời JSON để có thể nhận thấy hiệu ứng Shimmer.

[{
    "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"
}
]

Cùng bắt đầu sử dụng thư viện Shimmer bằng cách tạo một ứng dụng đơn giản.

3. Tạo project mới

Bước 1.

Tạo project mới bằng cách: File -> New Project và lựa chọn Basic Activity từ template

Bước 2.

Add Shimmer dependency vào build.gradle và rebuild lại 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'
}

Bước 3.

Thêm đoạn code về màu, dimen vào file như ở dưới

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>

Bước 4.

Tạo mới layout với tên là: recipe_placeholder_item.xml. Trong file này, chúng ta define layout bằng các view. Tất cả các view sẽ được đặt tương tự với các mục trong danh mục thực tế

<?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>

Bước 5.

Khi khung placeholder đã sẵn sàng, hãy thêm nó vào layout main activity của chúng tôi. Mở tệp activity_main.xml chúng ta include layout trình placeholder ba lần để làm cho nó xuất hiện dưới dạng danh sách.

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>

Bước 6.

Mở MainActivity.java và bắt đầu animation Shimmer bằng cách gọi phương thức startShimmerAnimation () trong onResume (). Chúng ta cũng tạm dừng animation ở onPause () khi hoạt động bị tạm dừng.

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

3.1. Load dữ liệu từ JSON và hiển thị trên Shimmer

Khi bộ tải Shimmer đã sẵn sàng, hãy xem cách tải nguồn cấp dữ liệu JSON trong RecyclerView và ẩn bộ tải lấp lánh khi danh sách được hiển thị. Bằng cách làm theo phần còn lại của bài hướng dẫn này, bạn sẽ hiểu làm thế nào để thực hiện hiệu ứng Shimmer trong một ứng dụng thực tế

Bước 7.

Mở build.gradle và add RecyclerView, Glide and Volley vào 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'
}

Bước 8.

Tạo 1 class với tên là MyApplication.java và kế thừa từ class Application. Đây là 1 class singleton, trong đó thư viện Volley sẽ đc khởi tạo

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

Bước 9.

Mở AndroidManifest.xml và thêm class MyApplication vào thẻ <application>. Chúng tôi cũng cần sự cho phép sử dụng INTERNET khi thực hiện các cuộc gọi http.

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>

Bước 10.

Mở file activity_main.xml và add RecyclerView vào dưới 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>

Bước 11.

Tạo 1 class với tên Recipe.java và define các giá trị như ở dưới. Dây là 1 class POJO được sử dụng để tuần tự hóa JSON.

Recipe.java
package info.androidhive.shimmer;
 
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;
    }
}

Bước 12.

Tạo một layout xml mới có tên recipe_list_item.xml.Layout này chứa các hàng danh sách thực tế với một hình nhỏ và vài 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>

Bước 13.

Chúng ta cần một class khác để ghi các adapter cần thiết cho RecyclerView. Tạo một class có tên là RecipeListAdapter.java và thêm đoạn code dưới đây.

RecipeListAdapter.java
package info.androidhive.shimmer;
 
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();
    }
}

Bước 14.

Mở MainActivity.java và sửa đổi mã như hình dưới đây.

fetchRecipes () tìm nạp JSON bằng cách thực hiện cuộc gọi http của Volley. JSON được phân tích cú pháp bằng cách sử dụng serializer Gson.

Một khi JSON phân tích cú pháp và thêm vào bộ điều hợp RecyclerView, danh sách sẽ được hiển thị và ShimmerFrameLayout bị giấu làm cho danh sách thực tế hiển thị trên màn hình.

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

Source