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

    1. Chuẩn bị

    Ở đâ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_width="match_parent"
        android:layout_height="match_parent"
        tools:context="edmt.dev.androidrcldynamic.MainActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="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_width="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_width="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_width="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_width="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_width="match_parent"
        android:layout_height="wrap_content">
    
        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="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à
    Nguồn: Viblo



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.