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 đ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:[email protected]'
    }
    

    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:[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'
    }
    

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

    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.