Android RecyclerView dynamically load more items when scroll to end with bottom ProgressBar

Android RecyclerView dynamically load more items when scroll to end with bottom ProgressBar

    Load more data when scrolling to bottom of list view/table view is one the most popular design style which available in a lot of application, for example: Facebook, Google+,... By my previous post, you've learn the way to do this trick with ListView. As you are already known, RecyclerView is a new component introduced in Android Lollipop, this component increases performances respect to ListView. Moreover, respect to ListView, RecyclerView is much more customizable.
    Today, with this post, I would like to talk about making an endless RecyclerView and when data is loading, it will show a ProgressBar at the bottom.

Adding dependencies to project

    The first work after creating a new Android Studio project is adding RecyclerView denpendency to your app level build.gradle. In this sample project, I set each RecyclerView item as a CardView, so you must add it's dependency, too:
dependencies {
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.android.support:recyclerview-v7:25.1.0'
    compile 'com.android.support:cardview-v7:25.1.0'
}

Create layout files

    Now we must provide some necessary XML files to define layouts. Because of we will set a ProgressBar at the bottom of RecyclerView when data is loading, it will have two item types. The normal item that to show info of user and loading item that place at bottom to show progress bar.
    Layout for the normal item:
item_recycler_view_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card_view:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:selectableItemBackground"
        android:padding="10dp">

        <TextView
            android:id="@+id/txt_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/txt_email"
            android:textColor="@android:color/black"
            android:textSize="12sp" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
    Layout for the loading item:
item_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>
    And you must put a RecyclerView object to the main activity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

Create a POJO class

    We've just create all necessary layouts for this project, now please move to programmatically code. Firstly, provide a POJO class (model object) simply like this:
Contact.java
package info.devexchanges.endlessrecyclerview;

public class Contact {
    private String email;
    private String phone;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Define an interface for callbacks

    Now, make an interface named OnLoadMoreListener with an abstract method onLoadMore() which will be invoked when we scroll the RecyclerView to end:
OnLoadMoreListener.java
package info.devexchanges.endlessrecyclerview;

public interface OnLoadMoreListener {
    void onLoadMore();
}

Configuration in RecyclerView adapter

    In this RecyclerView adapter (I named as ContactAdapter), we have 2 item types then must create two ViewHolder like below:
// "Loading item" ViewHolder
private class LoadingViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar progressBar;

        public LoadingViewHolder(View view) {
            super(view);
            progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
        }
    }

// "Normal item" ViewHolder
private class UserViewHolder extends RecyclerView.ViewHolder {
        public TextView phone;
        public TextView email;

        public UserViewHolder(View view) {
            super(view);
            phone = (TextView) view.findViewById(R.id.txt_phone);
            email = (TextView) view.findViewById(R.id.txt_email);
        }
    }
In this adapter, declare two constants that is delegate for two item type of RecyclerView:
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
Provide an OnLoadMoreListener variable and set an "add method":
private OnLoadMoreListener onLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.onLoadMoreListener = mOnLoadMoreListener;
    }
In the constructor of this adapter class, we handle scroll event of the RecyclerView here. This is the most important step, firstly, you must get LayoutManager of RecyclerView, detecting scroll to bottom in onScroll():
    private boolean isLoading;
    private Activity activity;
    private List<Contact> contacts;
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;

    public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
        this.contacts = contacts;
        this.activity = activity;

        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 (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    isLoading = true;
                }
            }
        });
    }
Add some necessary methods to complete ContactAdapter:
    @Override
    public int getItemViewType(int position) {
        return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_recycler_view_row, parent, false);
            return new UserViewHolder(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 UserViewHolder) {
            Contact contact = contacts.get(position);
            UserViewHolder userViewHolder = (UserViewHolder) holder;
            userViewHolder.phone.setText(contact.getEmail());
            userViewHolder.email.setText(contact.getPhone());
        } else if (holder instanceof LoadingViewHolder) {
            LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
            loadingViewHolder.progressBar.setIndeterminate(true);
        }
    }

    @Override
    public int getItemCount() {
        return contacts == null ? 0 : contacts.size();
    }

    public void setLoaded() {
        isLoading = false;
    }

Activity programmatically code

    In onCreate() method of activity, we must call setOnLoadMoreListener() and get new data inside onLoadMore(). This is full code of the activity:
MainActivity.java
package info.devexchanges.endlessrecyclerview;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private List<Contact> contacts;
    private ContactAdapter contactAdapter;
    private Random random;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        contacts = new ArrayList<>();
        random = new Random();

        //set dummy data
        for (int i = 0; i < 10; i++) {
            Contact contact = new Contact();
            contact.setPhone(phoneNumberGenerating());
            contact.setEmail("DevExchanges" + i + "@gmail.com");
            contacts.add(contact);
        }

        //find view by id and attaching adapter for the RecyclerView
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        contactAdapter = new ContactAdapter(recyclerView, contacts, this);
        recyclerView.setAdapter(contactAdapter);

        //set load more listener for the RecyclerView adapter
        contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if (contacts.size() <= 20) {
                    contacts.add(null);
                    contactAdapter.notifyItemInserted(contacts.size() - 1);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            contacts.remove(contacts.size() - 1);
                            contactAdapter.notifyItemRemoved(contacts.size());

                            //Generating more data
                            int index = contacts.size();
                            int end = index + 10;
                            for (int i = index; i < end; i++) {
                                Contact contact = new Contact();
                                contact.setPhone(phoneNumberGenerating());
                                contact.setEmail("DevExchanges" + i + "@gmail.com");
                                contacts.add(contact);
                            }
                            contactAdapter.notifyDataSetChanged();
                            contactAdapter.setLoaded();
                        }
                    }, 5000);
                } else {
                    Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private String phoneNumberGenerating() {
        int low = 100000000;
        int high = 999999999;
        int randomNumber = random.nextInt(high - low) + low;

        return "0" + randomNumber;
    }
}

Running application

    And this is output for our code:

Conclusions

    Over here, I've just present the way to put a progress bar at the bottom of RecyclerView and loading more data when scroll to end. This design is very popular in mobile application development, so I hope this post is helpful with your own work. Further, you can visit my previous post to learn solution with ListView or read related tutorial posts on other site like:
Populating multiple list views on a single Activity by using RecyclerView in Android

Populating multiple list views on a single Activity by using RecyclerView in Android

    With RecyclerView, building list view is now more simple, especially with new "scrolling mechanism", we now can put multiple RecyclerViews into a single screen (Activity or Fragment) without customizing in code.
    In this tip, I will present this solution through combining NestedScrollView and RecyclerView in the activity layout file. Of course, if you use ListView, please read my previous post to find out the way to expand it's height to maximum to display all list items.

Configuration in the layout (XML) file

    Make a NestedScrollView work as the root view and put 2 RecyclerView objects as it's children views to build this layout:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.devexchanges.multiplerecyclerview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:text="Asia"
            android:textStyle="bold" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_horizontal_margin"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:text="Europe"
            android:textStyle="bold" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>
    And this is layout for each RecyclerView item:
item_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/world"
            android:contentDescription="@null" />

        <TextView
            android:id="@+id/text"
            android:textColor="@color/colorPrimary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/image"
            android:layout_toEndOf="@+id/image"
            android:padding="10dp"
            android:text="@string/app_name" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
    NOTE: In order to use RecyclerView, CardView and NestedScrollView from Design Support Library, you must add these dependencies to your app level build.gradle:
compile 'com.android.support:design:25.1.0'
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.android.support:cardview-v7:25.1.0'

Set LayoutManager and data in programmatically code

    Back to your main activity programmatically code, there is no special thing here, please set LayoutManager for each RecyclerView (here is LinearLayoutManager) and initializing data/"adapter" for them:
MainActivity.java
package info.devexchanges.multiplerecyclerview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

public class MainActivity extends AppCompatActivity {

    private String[] asiaCountries = {"Vietnam", "China", "Japan", "Korea", "India", "Singapore", "Thailand", "Malaysia"};
    private String[] europeCountries = {"France", "Germany", "Sweden", "Denmark", "England", "Spain", "Portugal", "Norway"};

    private RecyclerView firstRecyclerView;
    private RecyclerView secondRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        firstRecyclerView = (RecyclerView)findViewById(R.id.recycler);
        secondRecyclerView = (RecyclerView)findViewById(R.id.recycler_1);

        //create and set layout manager for each RecyclerView
        RecyclerView.LayoutManager firstLayoutManager = new LinearLayoutManager(this);
        RecyclerView.LayoutManager secondLayoutManager = new LinearLayoutManager(this);

        firstRecyclerView.setLayoutManager(firstLayoutManager);
        secondRecyclerView.setLayoutManager(secondLayoutManager);

        //Initializing and set adapter for each RecyclerView
        RecyclerViewAdapter firstAdapter = new RecyclerViewAdapter(this, asiaCountries);
        RecyclerViewAdapter secondAdapter = new RecyclerViewAdapter(this, europeCountries);

        firstRecyclerView.setAdapter(firstAdapter);
        secondRecyclerView.setAdapter(secondAdapter);
    }
}
    And this is the sample "adapter" class for these RecyclerViews:
RecyclerViewAdapter.java
package info.devexchanges.multiplerecyclerview;

import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private Activity activity;
    private String[] strings;

    public RecyclerViewAdapter(Activity activity, String[] strings) {
        this.activity = activity;
        this.strings = strings;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = activity.getLayoutInflater();
        View view = inflater.inflate(R.layout.item_recycler_view, parent, false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        viewHolder.textView.setText(strings[position]);
    }

    @Override
    public int getItemCount() {
        return strings.length;
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private TextView textView;

        public ViewHolder(View view) {
            super(view);
            textView = (TextView) view.findViewById(R.id.text);
        }
    }
}

Running the project

    This is output of this sample project, you can see that I've build 2 list views into a single screen successful:

Final thought

    As you can see, with simple configuration in the activity layout file, you now can put make a screen which contains multiple list views by using RecyclerViews. Further, please check other posts about this widget on my blog:
Show/Hide layouts when scroll ListView in Android

Show/Hide layouts when scroll ListView in Android

   Detecting scrolling in ListView is fairly common in Android application development. To day, in this tip, I will share a simple solution to detect scroll reached to top/bottom of ListView and showing "header" and "footer" layout at this time. Another, when user scrolling, these layouts were disappeared.
   In order to do this, you must use setOnScrollListener() method of ListView and in onScrollListener() method, you provide some actions when scroll, reached to top/bottom.
Source code for this method can be like this:
public OnScrollListener onScrollListener() {
  return new OnScrollListener() {

   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
     int totalItemCount) {
    if (firstVisibleItem == 0) {
     // check if we reached the top or bottom of the list
     View v = listView.getChildAt(0);
     int offset = (v == null) ? 0 : v.getTop();
     if (offset == 0) {
      // reached the top: visible header and footer
      Log.i(TAG, "top reached");
      setViewStatus(footer, header, View.VISIBLE);
     }
    } else if (totalItemCount - visibleItemCount == firstVisibleItem) {
     View v = listView.getChildAt(totalItemCount - 1);
     int offset = (v == null) ? 0 : v.getTop();
     if (offset == 0) {
      // reached the bottom: visible header and footer
      Log.i(TAG, "bottom reached!");
      setViewStatus(footer, header, View.VISIBLE);
     }
    } else if (totalItemCount - visibleItemCount > firstVisibleItem){
     // on scrolling
     setViewStatus(footer, header, View.GONE);
     Log.i(TAG, "on scroll");
    }
   }
  };
 }
    In your activity, making some important methods (set data for ListView, set some "demo" event listener for buttons in "header/footer" layout, show/hide these items,...):

Full ReadingActivity.java code:
package com.blogspot.hongthaiit.mangareader;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class ReadingActivity extends Activity {

 private ListView listView;
 private ListViewAdapter adapter;
 private View btnLike, btnShare, btnNext, btnPrev, btnComment;
 private TextView chapterName;
 private ViewGroup header, footer;
 private final static String TAG = ReadingActivity.class.getSimpleName();

 private final static String[] imageArray = { "http://i.imgur.com/bRzOCig.jpg",
   "http://i.imgur.com/ykPVmnp.jpg", "http://i.imgur.com/RbKOKnG.jpg",
   "http://i.imgur.com/REjrtGp.jpg", "http://i.imgur.com/PT9QjQ9.jpg",
   "http://i.imgur.com/dg5NY1D.jpg", "http://i.imgur.com/Imlqk8j.jpg",
   "http://i.imgur.com/U2IPvXy.jpg", "http://i.imgur.com/0MNYYuG.jpg",
   "http://i.imgur.com/VZhAPel.jpg", "http://i.imgur.com/35jBerW.jpg",
   "http://i.imgur.com/0jHZEfn.jpg", "http://i.imgur.com/81HvMfA.jpg",
   "http://i.imgur.com/nobvCRi.jpg", "http://i.imgur.com/wdT3ZVw.jpg",
   "http://i.imgur.com/DV9MDja.jpg", "http://i.imgur.com/PN2TQnS.jpg" };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_reading);
  listView = (ListView) findViewById(R.id.list_view);
  locateListViewHeaderAndFooter();
  setListViewAdapter();
  
  //set on scrolling listener for ListView
  listView.setOnScrollListener(onScrollListener());
 }

 // adding header and footer for listview
 private void locateListViewHeaderAndFooter() {
  // declaring elements
  btnLike = findViewById(R.id.btn_like);
  btnComment = findViewById(R.id.btn_comment);
  btnNext =  findViewById(R.id.btn_next);
  btnPrev = findViewById(R.id.btn_prev);
  btnShare = findViewById(R.id.btn_share);
  chapterName = (TextView) findViewById(R.id.chapter_name);
  header = (ViewGroup) findViewById(R.id.layout_header);
  footer = (ViewGroup) findViewById(R.id.layout_footer);
  chapterName.setText("One Piece chap 650");

  // set "demo" events for buttons
  btnLike.setOnClickListener(onButtonClick("You liked it"));
  btnComment.setOnClickListener(onButtonClick("Please enter your comment..."));
  btnNext.setOnClickListener(onButtonClick("Go to Next chap ter"));
  btnPrev.setOnClickListener(onButtonClick("Go to Previous chap ter"));
  btnShare.setOnClickListener(onButtonClick("Thanks for sharing..."));


 }
 
 private void setListViewAdapter() {
  adapter = new ListViewAdapter(this, R.layout.item_listview, imageArray);
  listView.setAdapter(adapter);
 }

 public OnScrollListener onScrollListener() {
  return new OnScrollListener() {

   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
     int totalItemCount) {
    if (firstVisibleItem == 0) {
     // check if we reached the top or bottom of the list
     View v = listView.getChildAt(0);
     int offset = (v == null) ? 0 : v.getTop();
     if (offset == 0) {
      // reached the top: visible header and footer
      Log.i(TAG, "top reached");
      setViewStatus(footer, header, View.VISIBLE);
     }
    } else if (totalItemCount - visibleItemCount == firstVisibleItem) {
     View v = listView.getChildAt(totalItemCount - 1);
     int offset = (v == null) ? 0 : v.getTop();
     if (offset == 0) {
      // reached the bottom: visible header and footer
      Log.i(TAG, "bottom reached!");
      setViewStatus(footer, header, View.VISIBLE);
     }
    } else if (totalItemCount - visibleItemCount > firstVisibleItem){
     // on scrolling
     setViewStatus(footer, header, View.GONE);
     Log.i(TAG, "on scroll");
    }
   }
  };
 }

 // handle buttons events
 private OnClickListener onButtonClick(final String buttonEventName) {
  return new OnClickListener() {

   @Override
   public void onClick(View v) {
    Toast.makeText(ReadingActivity.this, buttonEventName, Toast.LENGTH_SHORT).show();
   }
  };
 }

 private void setViewStatus(ViewGroup vg1, ViewGroup vg2, int status) {
  vg1.setVisibility(status);
  vg2.setVisibility(status);
 }
}
    Creating a customize adapter for ListView:
ListViewAdapter.java
package com.blogspot.hongthaiit.mangareader;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;

import com.squareup.picasso.Picasso;

public class ListViewAdapter extends ArrayAdapter {

 private Activity activity;

 public ListViewAdapter(Activity activity, int resource, String[] objects) {
  super(activity, resource, objects);
  this.activity = activity;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  ViewHolder holder;
  LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
  // If holder not exist then locate all view from UI file.
  if (convertView == null) {
   // inflate UI from XML file
   convertView = inflater.inflate(R.layout.item_listview, parent, false);
   // get all UI view
   holder = new ViewHolder(convertView);
   // set tag for holder
   convertView.setTag(holder);
  } else {
   // if holder created, get tag from view
   holder = (ViewHolder) convertView.getTag();
  }
  
  //load image from url by Picasso library
  Picasso.with(activity).load(getItem(position)).into(holder.page);
  
  return convertView;
 }

 private class ViewHolder {
  private ImageView page;

  public ViewHolder(View v) {
   page = (ImageView) v.findViewById(R.id.image);
  }
 }
}
    Over here, programmatically codes completed. Declaring necessary layouts in xml files:
  • layout_header.xml: the view above ListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/layout_footer"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="#91000000"
   android:orientation="horizontal"
   android:padding="5dp" >
 
    <ImageView
       android:id="@+id/btn_prev"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="0.2"
       android:contentDescription="@string/app_name"
       android:src="@drawable/previous" />
 
    <TextView
       android:id="@+id/chapter_name"
       android:layout_width="0dp"
       android:textStyle="bold"
       android:layout_gravity="center"
       android:gravity="center"
       android:layout_height="wrap_content"
       android:layout_weight="0.6"
       android:textColor="@android:color/white" />
 
    <ImageView
       android:id="@+id/btn_next"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="0.2"
       android:contentDescription="@string/app_name"
       android:src="@drawable/next" />
 
</LinearLayout>
  • layout_footer.xml: the view below ListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/layout_header"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="#91000000"
   android:orientation="horizontal"
   android:padding="5dp" >
 
    <ImageView
       android:id="@+id/btn_like"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="0.33"
       android:contentDescription="@string/app_name"
       android:src="@drawable/like" />
 
    <ImageView
       android:id="@+id/btn_comment"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="0.33"
       android:contentDescription="@string/app_name"
       android:src="@drawable/comment" />
 
    <ImageView
       android:id="@+id/btn_share"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="0.33"
       android:contentDescription="@string/app_name"
       android:src="@drawable/share" />
 
</LinearLayout>
  • activity_reading.xml: activity layout, we'll include 2 above items here:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/container"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
 
    <ListView
       android:id="@+id/list_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" >
    </ListView>
 
    <include
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       layout="@layout/layout_header" />
 
    <include
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentBottom="true"
       layout="@layout/layout_footer" />
 
</RelativeLayout>
  • item_listview.xml: each ListView item layout - only include an ImageView:
item_listview.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:padding="5dp" >
 
    <ImageView
       android:id="@+id/image"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:contentDescription="@string/action_settings"
       android:scaleType="fitXY" />
 
</LinearLayout>
    Running program, our result will be liked this:
pic name pic name pic name

    NOTE:

In this post, I use Picasso library to load images from urls:
- See my PREVIOUS POST to learn how to use it.
- Visit Picasso Home Page to deep understanding it.

(sorry for ads)
Android ListView dynamically load more items when scroll to bottom

Android ListView dynamically load more items when scroll to bottom

    In this tutorial I am discussing about adding load more functionality to listview. It will be useful when you want to show data into paged format instead of loading huge amount of data into ListView.
    This project load data (JSON) from an URL to parse. Project descriptions:
  • Loading JSON data from an URL by using an AsyncTask and parse it to my own object.
  • In ListView, showing only 5 items for first time.
  • Using OnScrollListener() for ListView to detect scrolling reach to it's bottom.
  • Continue loading data and show in ListView.

Update info

With Material Design technology, RecyclerView is the alternate widget for ListView in creating list view/table view now. Please read my newer post if you would like to make an endless RecyclerView with a ProgressBar at the bottom when data is loading...
1. Start Eclipse or Android Studio and create a new android project.
2. Create an own POJO to use for ListView item:
CountryInfor.java
package com.blogspot.hongthaiit.androidloadmorelistview;

public class CountryInfor {
 private String id;
 private String name;
 
 public String getId() {
  return id;
 }
 
 public void setId(String id) {
  this.id = id;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
}
3. Custom an AsyncTask to downloading data from Internet:
LoadCountriesFromUrlTask.java
package com.blogspot.hongthaiit.androidloadmorelistview;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.util.Log;

public class LoadCountriesFromUrlTask extends AsyncTask<Void, Void, String> {

 private Activity activity;
 private String url;
 private ProgressDialog dialog;
 private final static String TAG = LoadCountriesFromUrlTask.class.getSimpleName();

 public LoadCountriesFromUrlTask(Activity activity, String url) {
  super();
  this.activity = activity;
  this.url = url;
 }

 @Override
 protected void onPreExecute() {
  super.onPreExecute();
  // Create a progress dialog
  dialog = new ProgressDialog(activity); 
  // Set progress dialog title
  dialog.setTitle("ListView Load More Tutorial");
  // Set progress dialog message
  dialog.setMessage("Loading more...");
  dialog.setIndeterminate(false);
  // Show progress dialog
  dialog.show(); 
 }

 @Override
 protected String doInBackground(Void... params) {

  // call load JSON from url method
  return loadJSON(this.url).toString();
 }

 @Override
 protected void onPostExecute(String result) {
  ((MainActivity) activity).parseJsonResponse(result);
  dialog.dismiss();
  Log.i(TAG, result);
 }

 public JSONObject loadJSON(String url) {
  // Creating JSON Parser instance
  JSONParser jParser = new JSONParser();

  // getting JSON string from URL
  JSONObject json = jParser.getJSONFromUrl(url);

  return json;
 }

 private class JSONParser {

  private InputStream is = null;
  private JSONObject jObj = null;
  private String json = "";

  // constructor
  public JSONParser() {

  }

  public JSONObject getJSONFromUrl(String url) {

   // Making HTTP request
   try {
    // defaultHttpClient
    DefaultHttpClient httpClient = new DefaultHttpClient();
    HttpPost httpPost = new HttpPost(url);

    HttpResponse httpResponse = httpClient.execute(httpPost);
    HttpEntity httpEntity = httpResponse.getEntity();
    is = httpEntity.getContent();

   } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
   } catch (ClientProtocolException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }

   try {
    BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"),
      8);
    StringBuilder sb = new StringBuilder();
    String line = null;
    while ((line = reader.readLine()) != null) {
     sb.append(line + "\n");
    }
    is.close();
    json = sb.toString();
   } catch (Exception e) {
    Log.e("Buffer Error", "Error converting result " + e.toString());
   }

   // try parse the string to a JSON object
   try {
    jObj = new JSONObject(json);
   } catch (JSONException e) {
    Log.e("JSON Parser", "Error parsing data " + e.toString());
   }

   // return JSON String
   return jObj;
  }
 }
}
4. Create an activity to run:
- It's layout simple like this:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#000055"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/country"
            android:textColor="@android:color/white"
            android:textSize="22sp" />
    </LinearLayout>

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/layout"
        android:scrollbars="none" />

</RelativeLayout>
- In activity programmatically code, we must set OnScrollListener for the ListView:

private OnScrollListener onScrollListener() {
  return new OnScrollListener() {

   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
    int threshold = 1;
    int count = listView.getCount();

    if (scrollState == SCROLL_STATE_IDLE) {
     if (listView.getLastVisiblePosition() >= count - threshold && pageCount < 2) {
      Log.i(TAG, "loading more data");
      // Execute LoadMoreDataTask AsyncTask
      getDataFromUrl(url_page2);
     }
    }
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
     int totalItemCount) {
   }

  };
 }
And this is the main activity full code:
MainActivity.java
package com.blogspot.hongthaiit.androidloadmorelistview;

import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;

public class MainActivity extends Activity {

 private final static String url_page1 = "http://www.json-generator.com/api/json/get/ckDtZPkgJe?indent=2";
 private final static String url_page2 = "http://www.json-generator.com/api/json/get/bVifgkcmxu?indent=2";
 private final static String TAG = MainActivity.class.getSimpleName();
 
 private int pageCount = 0;
 
 private CountryAdapter adapter;
 private ListView listView;
 private ProgressDialog dialog;
 private ArrayList<CountryInfor> countries;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  listView = (ListView) findViewById(R.id.list);
  setListViewAdapter();
  getDataFromUrl(url_page1);
  listView.setOnScrollListener(onScrollListener());
 }

 private void setListViewAdapter() {
  countries = new ArrayList<CountryInfor>();
  adapter = new CountryAdapter(this, R.layout.item_listview, R.id.country_name, countries);
  listView.setAdapter(adapter);
 }
 
 // calling asynctask to get json data from internet
 private void getDataFromUrl(String url) {
  new LoadCountriesFromUrlTask(this, url).execute();
 }

 private OnScrollListener onScrollListener() {
  return new OnScrollListener() {

   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
    int threshold = 1;
    int count = listView.getCount();

    if (scrollState == SCROLL_STATE_IDLE) {
     if (listView.getLastVisiblePosition() >= count - threshold && pageCount < 2) {
      Log.i(TAG, "loading more data");
      // Execute LoadMoreDataTask AsyncTask
      getDataFromUrl(url_page2);
     }
    }
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
     int totalItemCount) {
   }

  };
 }

 //parsing json after getting from Internet
 public void parseJsonResponse(String result) {
  Log.i(TAG, result);
  pageCount++;
  try {
   JSONObject json = new JSONObject(result);
   JSONArray jArray = new JSONArray(json.getString("message"));
   for (int i = 0; i < jArray.length(); i++) {
    
    JSONObject jObject = jArray.getJSONObject(i);
    CountryInfor country = new CountryInfor();
    country.setId(jObject.getString("id"));
    country.setName(jObject.getString("name"));
    countries.add(country);
   }
   
   adapter.notifyDataSetChanged();
   if (dialog != null) {
    dialog.dismiss();
   }
  } catch (JSONException e) {
   e.printStackTrace();  
  }
 }
}
5. Layout for each ListView row:
item_listview.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/country_name"
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:drawableLeft="@drawable/earth"
        android:drawablePadding="10dp"
        android:textColor="@android:color/holo_green_dark"
        android:textSize="20sp" />

</LinearLayout>
6. Custom a ListView adapter based on ArrayAdapter:
CountryAdapter.java
package com.blogspot.hongthaiit.androidloadmorelistview;

import java.util.List;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class CountryAdapter extends ArrayAdapter<CountryInfor> {

 private Activity activity;

 public CountryAdapter(Activity activity, int resource, int textViewResourceId,
   List<CountryInfor> countries) {
  super(activity, resource, textViewResourceId, countries);
  this.activity = activity;

 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {

  ViewHolder holder = null;
        LayoutInflater inflater = 
                (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        // If holder not exist then locate all view from UI file.
        if (convertView == null) {
            // inflate UI from XML file
            convertView = inflater.inflate(R.layout.item_listview, parent, false);
            // get all UI view
            holder = new ViewHolder(convertView);
            // set tag for holder
            convertView.setTag(holder);
        }  else {
            // if holder created, get tag from view
            holder = (ViewHolder) convertView.getTag();
        }
  
  CountryInfor country = getItem(position);
  
  holder.countryName.setText(country.getName());
  holder.countryName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.earth, 0, 0, 0);

  return convertView;
 }

 private static class ViewHolder {
  private TextView countryName;

  public ViewHolder(View v) {
   countryName = (TextView) v.findViewById(R.id.country_name);
  }
 }
}
7.  Open your AndroidManifest.xml file and add Internet permission:
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.hongthaiit.androidloadmorelistview"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />
    
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
8. Build and run program, we'll see our result: :D

pic name pic name pic name
(sorry for ads)