He who knows is better than he who knows, and he who knows is better than he who enjoys.

Abstract

Has been in the use of RecyclerView will slightly quiver, because has not been to understand how to achieve pull up loading, fed up with Github every time to find open source to introduce, because the feeling is for a pull up loading function to introduce a lot of code you do not know how many bugs, not only increased the degree of redundancy of the project, And bugs, you only to find that it’s hard to change, because of this, I have been determined to understand how to achieve the function of RecyclerView tensile load on the, I believe you and I had the same situation, but I believe that as long as you give yourself a few minutes to read this article, you’ll find that implements a tensile load is very simple.

What is pull-up loading

Pull-up loading and pull-down refresh correspond. After Android API LEVEL 19(4.4), Google officially launched the common use of SwipeRefreshLayout and RecyclerView, providing us with a more convenient drop-down refresh function of the list. However, It does not provide us with the pull-up loading function, but under the powerful extensibility of RecyclerView, there are many open source projects on Github to realize the pull-up loading function, that is, we will not load all the data into the list at one time, when the user slides to the bottom, then request data to the server, and then fill the data into the list. This can not only have better human-computer interaction, but also reduce the pressure on the server and improve the performance of the client. This article mainly introduces the implementation of the following simple pull-up loading function, students can master the most basic implementation function, and then through extension and optimization, or even encapsulation into a more general code, open source to Github.

Demo

Implementation approach

First, XML implementation

The layout is very simple, only a SwipeRefreshLayout wrapped a RecyclerView, I believe that used RecyclerView are easy to understand. Here 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>Copy the code

Then, the Item layout of RecyclerView is also very simple, with only one TextView. Item.xml is as follows:

<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>Copy the code

As you can see from our renderer, when we pull up, there is also a prompt entry, 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>Copy the code

Initialize SwipeRefreshLayout

We need to initialize the SwipeRefreshLayout. We need to initialize the SwipeRefreshLayout. It is easy to initialize the SwipeRefreshLayout.

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() {
    // Set visible
    refreshLayout.setRefreshing(true);
    // Reset the Adapter data source to null
    adapter.resetDatas();
    // Get columns 0 through PAGE_COUNT (value 10)
    updateRecyclerView(0, PAGE_COUNT);
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            // Simulate network load time, Settings invisible
            refreshLayout.setRefreshing(false); }},1000);
}Copy the code

3. Define the Adapter of RecyclerView

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<String> datas; / / the data source
    private Context context;    // Context Context

    private int normalType = 0;     // The first ViewType is a normal item
    private int footType = 1;       // The second type of ViewType, the bottom prompt View

    private boolean hasMore = true;   // Variable, whether there is more data
    private boolean fadeTips = false; // If the bottom hint is hidden

    private Handler mHandler = new Handler(Looper.getMainLooper()); // Get the main thread Handler

    public MyAdapter(List<String> datas, Context context, boolean hasMore) {
        // Initialize variables
        this.datas = datas;
        this.context = context;
        this.hasMore = hasMore;
    }

    // Get the number of entries, which is increased by 1 because we added a footView
    @Override
    public int getItemCount() {
        return datas.size() + 1;
    }

    // Custom method to get the last position of the data source in the list, 1 less than getItemCount because footView is not counted
    public int getRealLastPosition() {
        return datas.size();
    }


    // Return ViewType based on the item location for different holders in the onCreateViewHolder method
    @Override
    public int getItemViewType(int position) {
        if (position == getItemCount() - 1) {
            return footType;
        } else {
            returnnormalType; }}// The normal item ViewHolder is used 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 of footView at the bottom 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) {
        // Bind different layout files according to the returned ViewType. 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 is a normal IMTE, set the value of TextView directly
        if (holder instanceof NormalHolder) {
            ((NormalHolder) holder).textView.setText(datas.get(position));
        } else {
            // I made it visible because I hid the footView when THERE was no more data
            ((FootHolder) holder).tips.setVisibility(View.VISIBLE);
            // hasMore is false only when the fetch data is empty, so when we pull to the bottom we will almost always say "loading more..." first.
            if (hasMore == true) {
                // Do not hide the footView prompt
                fadeTips = false;
                if (datas.size() > 0) {
                    // If the query data is found to have increased, it shows that more data is being loaded
                    ((FootHolder) holder).tips.setText("Loading more..."); }}else {
                if (datas.size() > 0) {
                    // If there is no increase in the query data, no more data is displayed
                    ((FootHolder) holder).tips.setText("There's no more data.");

                    // Then load the simulated network request time through delay, execute after 500ms
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            // Hide the tooltip
                            ((FootHolder) holder).tips.setVisibility(View.GONE);
                            // Set fadeTips to true
                            fadeTips = true;
                            // hasMore is set to true so that when you pull to the bottom again, it will show that more is being loaded first
                            hasMore = true; }},500); }}}}// Expose the interface and change the method of fadeTips
    public boolean isFadeTips() {
        return fadeTips;
    }

    // Expose the interface, using the expose method to nullify the data source while pulling down the refresh
    public void resetDatas() {
        datas = new ArrayList<>();
    }

    // Expose the interface, update the data source, and change the value of hasMore. HasMore is true if data is added, false otherwise
    public void updateList(List<String> newDatas, boolean hasMore) {
        // Add new data to existing data
        if(newDatas ! =null) {
            datas.addAll(newDatas);
        }
        this.hasMore = hasMore; notifyDataSetChanged(); }}Copy the code

Four, initialize RecyclerView

private void initRecyclerView() {
    // Initialize the Adapter of RecyclerView
    // The first parameter is data. The principle of pull-up loading is pagination, so I set the constant PAGE_COUNT=10, that is, 10 data at a time
    // The second argument is Context
    // The third parameter is hasMore, whether there is 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());

    // Realize the important step of pull up loading, set the slide listener, RecyclerView built-in ScrollListener
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            // When newState is to slide to the bottom
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                // If the footView is not hidden, then the position of the last entry is 1 less than our getItemCount
                if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) {
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            // then call updateRecyclerview to updateRecyclerviewupdateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); }},500);
                }

                // If the prompt bar is hidden and we pull up again, then the last entry will be 2 less than getItemCount
                if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            // then call updateRecyclerview to updateRecyclerviewupdateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); }},500);
                }
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            // After the slide completes, get the position of the last visible itemlastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); }}); }// Update RecyclerView method to be called when pulling up
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); }}Copy the code

So, the complete code for the 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("Items" + 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 voidrun() { updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); }},500);
                    }

                    if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public voidrun() { 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); }}Copy the code

The latter

The above code I took into account more boundary conditions, so it was a little bit more in the code, but it didn’t affect the viewing. You can also change the number of data sources and PAGE_COUNT to test, everyone will have different requirements on the specific use, so I set out basic code, all tastes, more details need to optimize, the article footView can set up an animation, for example, the drop-down refresh replace with other style native style and so on, I think, These will be easy questions for you by the end of this article.

Download the Demo

Github Download: PullToLoadData-RecyclerView

CSDN resources: PullToLoadData-RecyclerView