Tải dữ liệu động với Recycler View
Trong quá trình sử dụng app Android , chắc hẳn nhiều khi các bạn đã thấy các hoạt động như là có 1 danh sách 10 bài viết , kéo xuống đọc hết 10 bài thì nó lại ra tiếp 10 bài nữa. Như thế này này : Điều này rất là dễ dàng giúp cho chúng ta có thể điều khiển được việc tải dữ liệu , tránh việc ...
Trong quá trình sử dụng app Android , chắc hẳn nhiều khi các bạn đã thấy các hoạt động như là có 1 danh sách 10 bài viết , kéo xuống đọc hết 10 bài thì nó lại ra tiếp 10 bài nữa. Như thế này này :
Điều này rất là dễ dàng giúp cho chúng ta có thể điều khiển được việc tải dữ liệu , tránh việc tải dư thừa dữ liệu. Chẳng hạn có 1000 bài viết thì mình cần lấy 10 bài mới nhất cho người dùng đọc thôi , còn người ta muốn đọc tiếp thì tải tiếp chẳng hạn...
Vậy trong bài hướng dẫn hôm nay , mình xin hướng dẫn các bạn kỹ thuật Dynamic load data trong Recycler View
Ở đây , vì bài này sử dụng RecyclerView nên mình xin yêu cầu các bạn trước khi bắt tay vào làm ví dụ này thì nên biết rõ cách tạo 1 RecyclerView và hiển thị dữ liệu lên nó đã RecyclerView thì chúng ta cần cài thư viện đó đã Trong ví dụ này , mình sử dụng thêm cardview để tạo danh sách thẻ cho nó trông thật là bắt mắt hơn 1 tí
Đầu tiên , tạo 1 interface ILoadmore để xử lý việc tải thêm dữ liệu sau
public interface ILoadMore { void onLoadMore(); }
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:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" tools:context="edmt.dev.androidrcldynamic.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_awidth="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
Tạo 2 layout mới , item_layout dành cho việc hiển thị các item và item_loading dành cho hiển thị progress ring
item_layout.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_awidth="match_parent" android:layout_height="wrap_content" app:cardElevation="10dp" android:layout_margin="8dp" > <LinearLayout android:orientation="vertical" android:padding="10dp" android:background="?android:selectableItemBackground" android:layout_awidth="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/txtName" android:text="Name" android:textColor="@android:color/black" android:textSize="16sp" android:textStyle="bold" android:layout_awidth="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/txtLength" android:text="Length" android:textColor="@android:color/black" android:textSize="14sp" android:layout_awidth="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </android.support.v7.widget.CardView>
item_loading.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_awidth="match_parent" android:layout_height="wrap_content"> <ProgressBar android:id="@+id/progressBar" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout>
RecyclerView luôn cần có Recycler.Adapter , tạo 1 lớp MyAdapter.java
package edmt.dev.androidrcldynamic.Adapter; import android.app.Activity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; import java.util.List; import edmt.dev.androidrcldynamic.Interface.ILoadMore; import edmt.dev.androidrcldynamic.Model.Item; import edmt.dev.androidrcldynamic.R; /** * Created by reale on 10/10/2017. */ class LoadingViewHolder extends RecyclerView.ViewHolder { public ProgressBar progressBar; public LoadingViewHolder(View itemView) { super(itemView); progressBar = (ProgressBar)itemView.findViewById(R.id.progressBar); } } class ItemViewHolder extends RecyclerView.ViewHolder{ public TextView name,length; public ItemViewHolder(View itemView) { super(itemView); name = (TextView)itemView.findViewById(R.id.txtName); length = (TextView)itemView.findViewById(R.id.txtLength); } } public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final int VIEW_TYPE_ITEM=0,VIEW_TYPE_LOADING=1; ILoadMore loadMore; boolean isLoading; Activity activity; List<Item> items; int visibleThreshold=5; int lastVisibleItem,totalItemCount; public MyAdapter(RecyclerView recyclerView,Activity activity, List<Item> items) { this.activity = activity; this.items = items; final LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); if(!isLoading && totalItemCount <= (lastVisibleItem+visibleThreshold)) { if(loadMore != null) loadMore.onLoadMore(); isLoading = true; } } }); } @Override public int getItemViewType(int position) { return items.get(position) == null ? VIEW_TYPE_LOADING:VIEW_TYPE_ITEM; } public void setLoadMore(ILoadMore loadMore) { this.loadMore = loadMore; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == VIEW_TYPE_ITEM) { View view = LayoutInflater.from(activity) .inflate(R.layout.item_layout,parent,false); return new ItemViewHolder(view); } else if(viewType == VIEW_TYPE_LOADING) { View view = LayoutInflater.from(activity) .inflate(R.layout.item_loading,parent,false); return new LoadingViewHolder(view); } return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof ItemViewHolder) { Item item = items.get(position); ItemViewHolder viewHolder = (ItemViewHolder) holder; viewHolder.name.setText(items.get(position).getName()); viewHolder.length.setText(String.valueOf(items.get(position).getLength())); } else if(holder instanceof LoadingViewHolder) { LoadingViewHolder loadingViewHolder = (LoadingViewHolder)holder; loadingViewHolder.progressBar.setIndeterminate(true); } } @Override public int getItemCount() { return items.size(); } public void setLoaded() { isLoading = false; } }
Ở đây chúng ta tạo ra 2 ViewHolder tương ứng với 2 layout
Layout Loading (sẽ hiển thị khi chúng ta tải dữ liệu mới)
class LoadingViewHolder extends RecyclerView.ViewHolder { public ProgressBar progressBar; public LoadingViewHolder(View itemView) { super(itemView); progressBar = (ProgressBar)itemView.findViewById(R.id.progressBar); } }
Layout Item (các item sẽ hiển thị theo thiết kế này)
class ItemViewHolder extends RecyclerView.ViewHolder{ public TextView name,length; public ItemViewHolder(View itemView) { super(itemView); name = (TextView)itemView.findViewById(R.id.txtName); length = (TextView)itemView.findViewById(R.id.txtLength); } }
Mấu chốt của tutorial nằm ở đây , ta sẽ xử lý việc tải thêm dữ liệu bằng cách set sự kiện onScrollListener cho RecyclerView ở hàm khởi tạo của Adapter
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); // Lấy tổng số lượng item đang có lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); // Lấy vị trí của item cuối cùng if(!isLoading && totalItemCount <= (lastVisibleItem+visibleThreshold)) // Nếu không phải trạng thái loading và tổng số lượng item bé hơn hoặc bằng vị trí item cuối + số lượng item tối đa hiển thị { if(loadMore != null) loadMore.onLoadMore(); // Gọi interface Loadmore isLoading = true; } } });
Override hàm getItemViewType để xử lý kiểu của View
@Override public int getItemViewType(int position) { return items.get(position) == null ? VIEW_TYPE_LOADING:VIEW_TYPE_ITEM; // So sánh nếu item được get tại vị trí này là null thì view đó là loading view , ngược lại là item }
Override hàm onCreateViewHolder để xử lý việc hiển thị dựa trên kiểu của View (chúng ta có thể dùng cái này để custom các layout item của RecyclerView nhưng đó là ở bài khác)
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == VIEW_TYPE_ITEM) { View view = LayoutInflater.from(activity) .inflate(R.layout.item_layout,parent,false); return new ItemViewHolder(view); } else if(viewType == VIEW_TYPE_LOADING) { View view = LayoutInflater.from(activity) .inflate(R.layout.item_loading,parent,false); return new LoadingViewHolder(view); } return null; }
Và ở hàm onBindViewHolder , chúng ta sẽ so sánh các thể hiện của View để xử lý nó
if(holder instanceof ItemViewHolder) { Item item = items.get(position); ItemViewHolder viewHolder = (ItemViewHolder) holder; viewHolder.name.setText(items.get(position).getName()); viewHolder.length.setText(String.valueOf(items.get(position).getLength())); } else if(holder instanceof LoadingViewHolder) { LoadingViewHolder loadingViewHolder = (LoadingViewHolder)holder; loadingViewHolder.progressBar.setIndeterminate(true); }
Đừng quên tạo setter cho ILoadmore
public void setLoadMore(ILoadMore loadMore) { this.loadMore = loadMore; }
Okay , giờ tạo 1 Model item mẫu theo dạng sau : Tạo lớp Item.java
public class Item { private String name; private int length; public Item(String name, int length) { this.name = name; this.length = length; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } }
Các bạn có thể thấy , lớp Item này chỉ có 2 thuộc tính là name và length
Và bây giờ tiến hành code trong MainActivity thôi
public class MainActivity extends AppCompatActivity { List<Item> items = new ArrayList<>(); MyAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Random data random10Data(); //Init View RecyclerView recycler = (RecyclerView)findViewById(R.id.recycler); recycler.setLayoutManager(new LinearLayoutManager(this)); adapter = new MyAdapter(recycler,this,items); recycler.setAdapter(adapter); //Set Load more event adapter.setLoadMore(new ILoadMore() { @Override public void onLoadMore() { if(items.size() <= 50) // Change max size { items.add(null); adapter.notifyItemInserted(items.size()-1); new Handler().postDelayed(new Runnable() { @Override public void run() { items.remove(items.size()-1); adapter.notifyItemRemoved(items.size()); //Random more data int index = items.size(); int end = index+10; for(int i=index;i<end;i++) { String name = UUID.randomUUID().toString(); Item item = new Item(name,name.length()); items.add(item); } adapter.notifyDataSetChanged(); adapter.setLoaded(); } },3000); // Time to load }else{ Toast.makeText(MainActivity.this, "Load data completed !", Toast.LENGTH_SHORT).show(); } } }); } private void random10Data() { for(int i =0;i<10;i++) { String name = UUID.randomUUID().toString(); Item item = new Item(name,name.length()); items.add(item); } } }
Ở đây thì mình tạo ra các data ngẫu nhiên bằng hàm random10Data
private void random10Data() { for(int i =0;i<10;i++) { String name = UUID.randomUUID().toString(); // Tạo 1 chuỗi UUID ngẫu nhiên Item item = new Item(name,name.length()); // Tạo mới 1 item model items.add(item); // Add vào danh sách } }
Tạo adapter và set adapter cho recyclerView bình thường như Đan Trường những năm trước 2000 chưa nổi tiếng
adapter = new MyAdapter(recycler,this,items); recycler.setAdapter(adapter);
Đây mới là cái ăn tiền nè , chúng ta phải implement cái Loadmore interface chứ không là nãy giờ bạn công cốc đấy
adapter.setLoadMore(new ILoadMore() { @Override public void onLoadMore() { if(items.size() <= 50) // Bạn có thể change max giá trị load ở đây , load tới số lượng như này thì có kéo nữa cũng không load nữa , bỏ điều kiện này đi thì nó cứ thế mà load { items.add(null); // Add 1 cái null , để làm gì ? Quay lại cái Adapter của chúng ta mà thấy , nếu gặp item null thì nó sẽ coi đó là Loading View adapter.notifyItemInserted(items.size()-1); // Báo với adapter là có sự thay đổi new Handler().postDelayed(new Runnable() { // Cái này là mình giả lập , bạn có thể replace cái Handler này với hàm fetch tới Web API của các bạn để load dữ liệu @Override public void run() { items.remove(items.size()-1); // Remove thằng null khi nãy ra adapter.notifyItemRemoved(items.size()); // Báo là có sự thay đổi //Random dữ liệu int index = items.size(); int end = index+10; for(int i=index;i<end;i++) { String name = UUID.randomUUID().toString(); Item item = new Item(name,name.length()); items.add(item); } adapter.notifyDataSetChanged(); adapter.setLoaded(); } },3000); // Mình delay 3 giây để demo , trong thực tế thì thời gian này dựa trên việc tải dữ liệu nhanh hay chậm }else{ Toast.makeText(MainActivity.this, "Load data completed !", Toast.LENGTH_SHORT).show(); } } });
Okay và kết quả của chúng ta là