Hướng dẫn sử dụng Content Placeholder Animation giống như Facebook sử dụng Shimmer
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 ...
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.
Để 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_awidth=“wrap_content” android:layout_height="wrap_content" shimmer:duration="1000"> <View android:layout_awidth="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();
Để 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.
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 <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 <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_awidth="match_parent" android:layout_height="wrap_content" android:padding="@dimen/activity_padding"> <View android:id="@+id/thumbnail" android:layout_awidth="@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_awidth="150dp" android:layout_height="10dp" android:layout_marginBottom="10dp" android:layout_toRightOf="@id/thumbnail" android:background="@color/placeholder_bg" /> <View android:layout_awidth="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_awidth="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_awidth="match_parent" android:layout_height="@dimen/placeholder_text_height" android:layout_marginRight="100dp" android:background="@color/placeholder_bg" /> <View android:layout_awidth="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_awidth="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_awidth="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_awidth="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="vertical" shimmer:duration="800"> <LinearLayout android:layout_awidth="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"/> <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_awidth="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_awidth="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="vertical" shimmer:duration="800"> <LinearLayout android:layout_awidth="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_awidth="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_awidth="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_awidth="@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_awidth="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_awidth="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_awidth="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_awidth="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_awidth="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