I'm trying to figure out how to implement an infinitely scrolling list. It will display a calendar and events and it should start from now or selected date. It should be scrollable in both directions, past and future. The solutions with OnScrollListener here seem to work pretty well if I only need to go to future (index just grows bigger). But I don't see how I would go to the past.
This solution seems to be very wasteful for my case. getView is called thousands of times. Maybe ListView isn't the solution, and I'll have to go with lower-level code. Any ideas?
EDIT: getView being called thousands of times wasn't the fault of the latter solution. However, it still gets called too many times and with wrong values. If I set selection like this:
myList.setSelection(Integer.MAX_VALUE/2)
I get getView calls with indexes starting from zero. For example, I get getView calls like this:
getView pos 0
...
getView pos 26
and then
getView pos 1073741823
...
getView pos 1073741847
Which are the correct ones. Then:
getView pos 0
...
getView pos 26
again
This all happens before I scroll or touch the screen at all. Doesn't seem to make much sense.
Here is an implementation of this task.
EndlessScrollBaseAdapter.java
package com.example.endlessscrollinbothdirections;
import java.util.Map;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.TextView;
/** A child class shall subclass this Adapter and implement method getDataRow(int position,
* View convertView, ViewGroup parent), which supplies a View present data in a ListRow.
* This parent Adapter takes care of displaying ProgressBar in a row or indicating that it
* has reached the last row. */
public abstract class EndlessScrollBaseAdapter<T> extends BaseAdapter implements
OnScrollListener {
private int mVisibleThreshold = 5;
// the main data structure to save loaded data
protected Map<Integer, T> mItems;
protected Context mContext;
// the serverListSize is the total number of items on the server side,
// which should be returned from the web request results
protected int mServerListSize = -1;
// Two view types which will be used to determine whether a row should be displaying
// data or a Progressbar
public static final int VIEW_TYPE_LOADING = 0;
public static final int VIEW_TYPE_ACTIVITY = 1;
public static final int VIRTUAL_MIDDLE_OFFSET = Integer.MAX_VALUE / 2;
public EndlessScrollBaseAdapter(Context context, Map<Integer, T> items) {
mContext = context;
mItems = items;
}
public void setServerListSize(int serverListSize) {
this.mServerListSize = serverListSize;
}
/** disable click events on indicating rows */
@Override
public boolean isEnabled(int position) {
return getItemViewType(position) == EndlessScrollBaseAdapter.VIEW_TYPE_ACTIVITY;
}
/** One type is normal data row, the other type is Progressbar */
@Override
public int getViewTypeCount() {
return 2;
}
/** the size of the List plus one, the one is the last row, which displays a
* Progressbar */
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
/** return the type of the row, the last row indicates the user that the ListView is
* loading more data */
@Override
public int getItemViewType(int position) {
return mItems.containsKey(position
- EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET) ? EndlessScrollBaseAdapter.VIEW_TYPE_ACTIVITY
: EndlessScrollBaseAdapter.VIEW_TYPE_LOADING;
}
@Override
public T getItem(int position) {
return mItems.get(position - EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET);
}
@Override
public long getItemId(int position) {
return position;
}
/** returns the correct view */
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == EndlessScrollBaseAdapter.VIEW_TYPE_LOADING) {
return getFooterView(position, convertView, parent);
}
return getDataRow(position, convertView, parent);
};
/** A subclass should override this method to supply the data row.
*
* @param position
* @param convertView
* @param parent
* @return */
public abstract View getDataRow(int position, View convertView, ViewGroup parent);
/** returns a View to be displayed in the last row.
*
* @param position
* @param convertView
* @param parent
* @return */
public View getFooterView(int position, View convertView, ViewGroup parent) {
if (position >= mServerListSize && mServerListSize > 0) {
// the ListView has reached the last row
TextView tvLastRow = new TextView(mContext);
tvLastRow.setHint("Reached the last row.");
tvLastRow.setGravity(Gravity.CENTER);
return tvLastRow;
} else {
TextView tvLastRow = new TextView(mContext);
tvLastRow.setHint("Loading...\n position: " + position);
tvLastRow.setGravity(Gravity.CENTER);
return tvLastRow;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int virtualPosition);
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
for (int i = -mVisibleThreshold; i < visibleItemCount + mVisibleThreshold; i++) {
int virtualPosition = firstVisibleItem
- EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET + i;
onLoadMore(virtualPosition);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
EndlessScrollAdapter.java
package com.example.endlessscrollinbothdirections;
import java.util.Map;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class EndlessScrollAdapter extends EndlessScrollBaseAdapter<Integer> {
public EndlessScrollAdapter(Activity activity, Map<Integer, Integer> list) {
super(activity, list);
}
@Override
public View getDataRow(int position, View convertView, ViewGroup parent) {
TextView TextView;
if (convertView == null) {
TextView = new TextView(mContext);
} else {
TextView = (TextView) convertView;
}
TextView.setText("virtualPosition: "
+ (position - EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET) + "\n"
+ "row data: "
+ mItems.get(position - EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET));
return TextView;
}
@Override
public void onLoadMore(int virtualPosition) {
// here you might launch an AsyncTask instead
if (!mItems.containsKey(virtualPosition)) {
mItems.put(virtualPosition, virtualPosition);
notifyDataSetChanged();
}
}
}
MainActivity.java
package com.example.endlessscrollinbothdirections;
import java.util.HashMap;
import java.util.Map;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.ListView;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.lvItems);
Map<Integer, Integer> items = new HashMap<Integer, Integer>();
EndlessScrollAdapter endlessScrollAdapter = new EndlessScrollAdapter(this, items);
listView.setAdapter(endlessScrollAdapter);
listView.setSelection(EndlessScrollBaseAdapter.VIRTUAL_MIDDLE_OFFSET);
listView.setOnScrollListener(endlessScrollAdapter);
}
}
activity_main.xml
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lvItems"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With