Chicken Soup for the soul: those who know are inferior to those who are good, and those who are good are inferior to those who are happy.
abstract
I've been using RecyclerView with a little tremor because I haven't been able to understand how to implement pull-up loading, and I'm fed up with every time I go to Github to find open source introductions, because I feel like I'm introducing a lot of code for a pull-up loading function that you don't know how many BUGs there are, which not only increases the redundancy of the project, but also adds to it. Now BUG, you find it difficult to change, because of this, I decided to understand how to achieve the RecyclerView pull-up loading function, I believe you have the same situation as me, but I believe that as long as you give yourself a few minutes to read this article, you will find it is very important to achieve a pull-up loading. Simple.
What is pull-up loading
After Android API LEVEL 19(4.4), Google officially launched Swipe Refresh Layout and RecyclerView, providing us with a more convenient list drop-down refresh function. However, it did not provide us with the upload function, but it is powerful in RecyclerView. Extended, there are many open source projects on Github to implement the pull-up loading function, that is, we will not load all the data into the list at once, when the user slides to the bottom, then requests the data from the server, and fills the data into the list, so that not only can we have better human-computer interaction, but also reduce the number of clothes. The pressure of server also improves the performance of client. This article mainly introduces the implementation of the following simple pull-up loading function. After mastering the most basic implementation function, students can expand and optimize it, and even encapsulate it into more general code, open source to Github.
Ideas for Realization
1. Implementation of XML
The layout is simple. Only one Swipe Refresh Layout wraps a Recycler View, which I believe is easy to understand for anyone who has used Recycler View. The following is activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refreshLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
Then, our RecyclerView Item layout is also very simple, with only one TextView. The following is item.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="120dp" android:background="@android:color/holo_blue_dark" android:gravity="center" android:textSize="30sp" android:textColor="#ffffff" android:text="11" android:layout_marginBottom="1dp"/> </LinearLayout>
As you can see from our renderings, there is also a prompt entry when we pull up, which I define as footview.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tips" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="30dp" android:textSize="15sp" android:layout_marginBottom="1dp"/> </LinearLayout>
Initialization of SwipeRefreshLayout
After we have prepared the layout file, we turn our eyes to Activity. First, we need to initialize SwipeRefreshLayout. The initialization is very simple. The findView operation is omitted, so we only need to set the rotation color and the refresh listening event.
private void initRefreshLayout() { refreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light); refreshLayout.setOnRefreshListener(this); } @Override public void onRefresh() { // Settings visible refreshLayout.setRefreshing(true); // Reset adapter's data source to null adapter.resetDatas(); // Get the data from Articles 0 to PAGE_COUNT (value 10) updateRecyclerView(0, PAGE_COUNT); mHandler.postDelayed(new Runnable() { @Override public void run() { // Simulate network load time, set invisible refreshLayout.setRefreshing(false); } }, 1000); }
3. Defining Adadapter for RecyclerView
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List<String> datas; // data source private Context context; // Context private int normalType = 0; // The first view type, normal item private int footType = 1; // The second ViewType, the bottom prompt View private boolean hasMore = true; // Variables, is there more data? private boolean fadeTips = false; // Variables, whether the bottom hint is hidden private Handler mHandler = new Handler(Looper.getMainLooper()); //Get the Hadler of the main thread public MyAdapter(List<String> datas, Context context, boolean hasMore) { // initialize variable this.datas = datas; this.context = context; this.hasMore = hasMore; } // To get the number of entries, add 1 because of the addition of a footView @Override public int getItemCount() { return datas.size() + 1; } // Custom method to get the last location of the data source in the list, 1 less than getItemCount because footView is not included public int getRealLastPosition() { return datas.size(); } // Return ViewType according to the entry location for different Holders in the onCreateViewHolder method @Override public int getItemViewType(int position) { if (position == getItemCount() - 1) { return footType; } else { return normalType; } } // ViewHolder for normal item to cache findView operations class NormalHolder extends RecyclerView.ViewHolder { private TextView textView; public NormalHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.tv); } } // // ViewHolder at the bottom footView to cache findView operations class FootHolder extends RecyclerView.ViewHolder { private TextView tips; public FootHolder(View itemView) { super(itemView); tips = (TextView) itemView.findViewById(R.id.tips); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // Based on the ViewType returned, bind different layout files, there are only two if (viewType == normalType) { return new NormalHolder(LayoutInflater.from(context).inflate(R.layout.item, null)); } else { return new FootHolder(LayoutInflater.from(context).inflate(R.layout.footview, null)); } } @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { // If it's a normal imte, set the TextView value directly if (holder instanceof NormalHolder) { ((NormalHolder) holder).textView.setText(datas.get(position)); } else { // The reason for setting up visibility is that I hide this footView when I don't have more data. ((FootHolder) holder).tips.setVisibility(View.VISIBLE); // hasMore is false only when the data is empty, so when we pull to the bottom, we basically start by showing "Loading more..." if (hasMore == true) { // Do not hide footView hints fadeTips = false; if (datas.size() > 0) { // If the query data finds an increase, it shows that more is being loaded. ((FootHolder) holder).tips.setText("Loading more..."); } } else { if (datas.size() > 0) { // If the query data is not found to have increased, no more data is shown. ((FootHolder) holder).tips.setText("No more data"); // Then the time of loading the simulated network request is delayed and executed after 500 ms. mHandler.postDelayed(new Runnable() { @Override public void run() { // Hidden Tip Bar ((FootHolder) holder).tips.setVisibility(View.GONE); // Set fadeTips to true fadeTips = true; // hasMore is set to true to show that more is being loaded when pulled to the bottom again hasMore = true; } }, 500); } } } } // Exposure interface, change fadeTips method public boolean isFadeTips() { return fadeTips; } // Exposure interface, when drop-down refresh, the data source is empty by exposing method public void resetDatas() { datas = new ArrayList<>(); } // Expose the interface, update the data source, and modify the value of hasMore. If you add data, hasMore is true, otherwise it is false. public void updateList(List<String> newDatas, boolean hasMore) { // Add new data to the original data if (newDatas != null) { datas.addAll(newDatas); } this.hasMore = hasMore; notifyDataSetChanged(); } }
IV. Initialization of RecyclerView
private void initRecyclerView() { // Adapter Initializing RecyclerView // The first parameter is data, and the principle of pull-up loading is paging, so I set the constant PAGE_COUNT=10, that is, 10 data are loaded at a time. // The second parameter is Context // The third parameter is hasMore. Is there any new data? adapter = new MyAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false); mLayoutManager = new GridLayoutManager(this, 1); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setAdapter(adapter); recyclerView.setItemAnimator(new DefaultItemAnimator()); // Implementing important steps of pull-up loading, setting up sliding listener, ScrollListener with RecyclerView recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // When newState slides to the bottom if (newState == RecyclerView.SCROLL_STATE_IDLE) { // If you don't hide footView, then the location of the last entry is 1 less than our getItemCount, so you can figure it out for yourself if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { // Then call the updateRecyclerview method to update RecyclerView updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } // If the prompt bar is hidden and we pull it up again, the last entry will be 2 fewer than getItemCount if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { // Then call the updateRecyclerview method to update RecyclerView updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // After the sliding is completed, get the last visible item's position lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); } }); } // Update RecyclerView Method Called on Upload private void updateRecyclerView(int fromIndex, int toIndex) { // Get data from fromIndex to toIndex List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { // Then pass it to Adapter and set hasMore to true adapter.updateList(newDatas, true); } else { adapter.updateList(null, false); } }
So the complete code for Activity is as follows:
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener { private SwipeRefreshLayout refreshLayout; private RecyclerView recyclerView; private List<String> list; private int lastVisibleItem = 0; private final int PAGE_COUNT = 10; private GridLayoutManager mLayoutManager; private MyAdapter adapter; private Handler mHandler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); findView(); initRefreshLayout(); initRecyclerView(); } private void initData() { list = new ArrayList<>(); for (int i = 1; i <= 40; i++) { list.add("entry" + i); } } private void findView() { refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout); recyclerView = (RecyclerView) findViewById(R.id.recyclerView); } private void initRefreshLayout() { refreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light); refreshLayout.setOnRefreshListener(this); } private void initRecyclerView() { adapter = new MyAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false); mLayoutManager = new GridLayoutManager(this, 1); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setAdapter(adapter); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); } }); } private List<String> getDatas(final int firstIndex, final int lastIndex) { List<String> resList = new ArrayList<>(); for (int i = firstIndex; i < lastIndex; i++) { if (i < list.size()) { resList.add(list.get(i)); } } return resList; } private void updateRecyclerView(int fromIndex, int toIndex) { List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { adapter.updateList(newDatas, true); } else { adapter.updateList(null, false); } } @Override public void onRefresh() { refreshLayout.setRefreshing(true); adapter.resetDatas(); updateRecyclerView(0, PAGE_COUNT); mHandler.postDelayed(new Runnable() { @Override public void run() { refreshLayout.setRefreshing(false); } }, 1000); } }
Later words
I have considered more boundary conditions in the above code, so there will be a little more in the code, but it does not affect the viewing. You can also test by changing the number of data sources and PAGE_COUNT, etc. Everyone will have different requirements in the specific use, so the basic code I put out, difficult to adjust, more details need to be optimized, such as footView can set up an animation bar, drop-down refresh with other styles to replace the original. Styles of students and so on, I think, these will be simple questions for you to learn this article.
Demo Download
Github Download: PullToLoadData-RecyclerView
CSDN resources: PullToLoadData-RecyclerView