Hand-in-hand teaching you how to implement Android Recycler View pull-up loading function

Posted by jaykappy on Tue, 09 Jul 2019 23:30:49 +0200

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

Topics: Android github xml network