By user281076


2010-02-25 09:18:51 8 Comments

I want to create a customized ListView (or similar) which will behave like a closed (circular) one:

  1. scrolling down - after the last item was reached the first begins (.., n-1, n, 1, 2, ..)
  2. scrolling upward - after the first item was reached the last begins (.., 2, 1, n, n-1, ..)

It sounds simple conceptually but, apparently, there is no straightforward approach to do this. Can anyone point me to the right solution ? Thank you !

I have already received an answer (from Streets Of Boston on Android-Developers google groups), but it sounds somehow ugly :) -

I did this by creating my own list-adapter (subclassed from BaseAdapter).

I coded my own list-adapter in such a way that its getCount() method returns a HUUUUGE number.

And if item 'x' is selected, then this item corresponds to adapter position='adapter.getCount()/2+x'

And for my adapter's method getItem(int position), i look in my array that backs up the adapter and fetch the item on index: (position-getCount()/2) % myDataItems.length

You need to do some more 'special' stuff to make it all work correctly, but you get the idea.

In principle, it is still possible to reach the end or the beginning of the list, but if you set getCount() to around a million or so, this is hard to do :-)

5 comments

@Dawson 2011-02-09 00:15:48

My colleague Joe, and I believe we have found a simpler way to solve the same problem. In our solution though instead of extending BaseAdapter we extend ArrayAdapter.

The code is as follows :

public class CircularArrayAdapter< T > extends ArrayAdapter< T >
{   

        public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
        public final int MIDDLE;
        private T[] objects;

        public CircularArrayAdapter(Context context, int textViewResourceId, T[] objects)
        {
            super(context, textViewResourceId, objects);
            this.objects = objects;
            MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % objects.length;
        }

        @Override
        public int getCount()
        {
            return Integer.MAX_VALUE;
        }

        @Override
        public T getItem(int position) 
        {
            return objects[position % objects.length];
        }
 }

So this creates a class called CircularArrayAdapter which take an object type T (which may be anything) and uses it to create an array list. T is commonly a string though may be anything.

The constructor is the same as is for ArrayAdapter though initializes a constant called middle. This is the middle of the list. No matter what the length of the array MIDDLE can be used to center the ListView in the mid of the list.

getCount() is overrides to return a huge value as is done above creating a huge list.

getItem() is overrides to return the fake position on the array. Thus when filling the list the list is filled with objects in a looping manner.

At this point CircularArrayAdapter simply replaces ArrayAdapter in the file creating the ListView.

To centre the ListView the fallowing line must be inserted in your file creating the ListView after the ListView object has been initialised:

listViewObject.setSelectionFromTop(nameOfAdapterObject.MIDDLE, 0);

and using the MIDDLE constant previously initialized for the list the view is centered with the top item of the list at the top of the screen.

: ) ~ Cheers, I hope this solution is useful.

@Tom 2011-06-01 00:27:11

This should be the solution. This is amazingly simple and works flawlessly. Thank you

@Surej 2012-04-24 06:19:43

@Dawson: Whether the items will update while reach on the top (i.e) scrolling from bottom to top.after reach the first item again will show last items on top?

@nebulae 2012-08-25 20:59:24

Thank you for this, perfect!

@Sreedevi J 2013-08-29 06:52:07

I just came across this solution, and frankly, it's amazing how simple the code is. Kudos!

@Dawson 2013-10-31 14:43:50

@prodaea They manage themselves. If I have 19 items in the array and scroll to the bottom of the list, so thus my index is 0, everything is fine. When I scroll down an additional item, and thus my index is -1, -1 % 19 evaluates to 18 in java, which is the last element in my list. In a circular list the last element is logically previous to the first element, and thus we are fine. With this considered, MIDDLE should be set to 0, making it the midpoint between of Integer.MIN_VALUE and Integer.MAX_VALUE. I am glad you brought this up though because I had not considered this case.

@Viswanath Lekshmanan 2013-12-24 05:36:37

I dont think its a good idea, The use of adapter is it creates the elements in memory only when needed and here you are initializing adapter with the whole list of elements , Then it is not an adapter isn't ? Its just a manager just passes the data when needed.

@Razgriz 2014-12-08 09:36:51

Sorry to bump this a year later, but what is textViewResourceId?

@Dawson 2014-12-08 15:29:44

It is the resource id of the text view used to instantiate views. See here : developer.android.com/reference/android/widget/…, int)

@Yash Agrawal 2017-09-06 01:49:33

What is the role of MIDDLE here , because after initializing i dont see it being used anywhere else?.

@ViliusK 2013-01-31 00:15:27

If using LoadersCallbacks I have created MyCircularCursor class which wraps the typical cursor like this:

@Override
public void onLoadFinished(Loader<Cursor> pCursorLoader, Cursor pCursor) {
        mItemListAdapter.swapCursor(new MyCircularCursor(pCursor));
}

the decorator class code is here:

public class MyCircularCursor implements Cursor {

private Cursor mCursor;

public MyCircularCursor(Cursor pCursor) {
    mCursor = pCursor;
}

@Override
public int getCount() {
    return mCursor.getCount() == 0 ? 0 : Integer.MAX_VALUE;
}

@Override
public int getPosition() {
    return mCursor.getPosition();
}

@Override
public boolean move(int pOffset) {
    return mCursor.move(pOffset);
}

@Override
public boolean moveToPosition(int pPosition) {
    int position = MathUtils.mod(pPosition, mCursor.getCount());
    return mCursor.moveToPosition(position);
}

@Override
public boolean moveToFirst() {
    return mCursor.moveToFirst();
}

@Override
public boolean moveToLast() {
    return mCursor.moveToLast();
}

@Override
public boolean moveToNext() {
    if (mCursor.isLast()) {
        mCursor.moveToFirst();
        return true;
    } else {
        return mCursor.moveToNext();
    }
}

@Override
public boolean moveToPrevious() {
    if (mCursor.isFirst()) {
        mCursor.moveToLast();
        return true;
    } else {
        return mCursor.moveToPrevious();
    }
}

@Override
public boolean isFirst() {
    return false;
}

@Override
public boolean isLast() {
    return false;
}

@Override
public boolean isBeforeFirst() {
    return false;
}

@Override
public boolean isAfterLast() {
    return false;
}

@Override
public int getColumnIndex(String pColumnName) {
    return mCursor.getColumnIndex(pColumnName);
}

@Override
public int getColumnIndexOrThrow(String pColumnName) throws IllegalArgumentException {
    return mCursor.getColumnIndexOrThrow(pColumnName);
}

@Override
public String getColumnName(int pColumnIndex) {
    return mCursor.getColumnName(pColumnIndex);
}

@Override
public String[] getColumnNames() {
    return mCursor.getColumnNames();
}

@Override
public int getColumnCount() {
    return mCursor.getColumnCount();
}

@Override
public byte[] getBlob(int pColumnIndex) {
    return mCursor.getBlob(pColumnIndex);
}

@Override
public String getString(int pColumnIndex) {
    return mCursor.getString(pColumnIndex);
}

@Override
public short getShort(int pColumnIndex) {
    return mCursor.getShort(pColumnIndex);
}

@Override
public int getInt(int pColumnIndex) {
    return mCursor.getInt(pColumnIndex);
}

@Override
public long getLong(int pColumnIndex) {
    return mCursor.getLong(pColumnIndex);
}

@Override
public float getFloat(int pColumnIndex) {
    return mCursor.getFloat(pColumnIndex);
}

@Override
public double getDouble(int pColumnIndex) {
    return mCursor.getDouble(pColumnIndex);
}

@Override
public int getType(int pColumnIndex) {
    return 0;
}

@Override
public boolean isNull(int pColumnIndex) {
    return mCursor.isNull(pColumnIndex);
}

@Override
public void deactivate() {
    mCursor.deactivate();
}

@Override
@Deprecated
public boolean requery() {
    return mCursor.requery();
}

@Override
public void close() {
    mCursor.close();
}

@Override
public boolean isClosed() {
    return mCursor.isClosed();
}

@Override
public void registerContentObserver(ContentObserver pObserver) {
    mCursor.registerContentObserver(pObserver);
}

@Override
public void unregisterContentObserver(ContentObserver pObserver) {
    mCursor.unregisterContentObserver(pObserver);
}

@Override
public void registerDataSetObserver(DataSetObserver pObserver) {
    mCursor.registerDataSetObserver(pObserver);
}

@Override
public void unregisterDataSetObserver(DataSetObserver pObserver) {
    mCursor.unregisterDataSetObserver(pObserver);
}

@Override
public void setNotificationUri(ContentResolver pCr, Uri pUri) {
    mCursor.setNotificationUri(pCr, pUri);
}

@Override
public boolean getWantsAllOnMoveCalls() {
    return mCursor.getWantsAllOnMoveCalls();
}

@Override
public Bundle getExtras() {
    return mCursor.getExtras();
}

@Override
public Bundle respond(Bundle pExtras) {
    return mCursor.respond(pExtras);
}

@Override
public void copyStringToBuffer(int pColumnIndex, CharArrayBuffer pBuffer) {
    mCursor.copyStringToBuffer(pColumnIndex, pBuffer);
}
}

@Wesley 2012-08-16 06:36:30

I could see some good answers for this, One of my friend has tried to achieve this via a simple solution. Check the github project.

@Dory 2014-04-23 06:23:23

How can we create a two way circular list view,when scroll upward it should show last item. Can you give me any hint.

@Harry 2010-11-05 16:06:04

I have, or I think I have done it right, based on the answers above. Hope this will help you.

private static class RecipeListAdapter extends BaseAdapter {
    private static LayoutInflater   mInflater;
    private Integer[]               mCouponImages;
    private static ImageView        viewHolder;
    public RecipeListAdapter(Context c, Integer[] coupomImages) {
        RecipeListAdapter.mInflater = LayoutInflater.from(c);
        this.mCouponImages = coupomImages;
    }
    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public Object getItem(int position) {
       // you can do your own tricks here. to let it display the right item in your array.
        return position % mCouponImages.length;
    }

    @Override
    public long getItemId(int position) {
        return position;
        // return position % mCouponImages.length;
    }

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

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.coupon_list_item, null);
            viewHolder = (ImageView) convertView.findViewById(R.id.item_coupon);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ImageView) convertView.getTag();
        }

        viewHolder.setImageResource(this.mCouponImages[position %     mCouponImages.length]);
        return convertView;
    }

}

And you would like to do this if you want to scroll down the list. Commonly we can just scroll up and list then scroll down.

// see how many items we would like to sroll. in this case, Integer.MAX_VALUE

int listViewLength = adapter.getCount();

// see how many items a screen can dispaly, I use variable "span"
    final int span = recipeListView.getLastVisiblePosition() - recipeListView.getFirstVisiblePosition();

// see how many pages we have

int howManySpans = listViewLength / span;

// see where do you want to be when start the listview. you dont have to do the "-3" stuff. it is for my app to work right.

recipeListView.setSelection((span * (howManySpans / 2)) - 3);

@Romain Guy 2010-02-25 15:46:32

The solution you mention is the one I told other developers to use in the past. In getCount(), simply return Integer.MAX_VALUE, it will give you about 2 billion items, which should be enough.

@user281076 2010-02-26 13:54:26

Thanks for your answers. Hugh number solution :)

@Surej 2012-04-24 11:57:43

@Romain Guy: Using Hugh Number solution will make performance issue on trying for large number of data.

@Paul Lammertsma 2013-04-07 15:29:54

@Surej I believe you are trying to suggest that returning a "huge number" will impact performance. It will not; a brief review of the source reveals that ListViews don't actually allocate any objects based purely off this number. The only exception that I'm uncertain of is using layout mode LAYOUT_FORCE_BOTTOM as it populates from the bottom up, but I didn't look at it in detail.

@Romain Guy 2013-04-08 17:05:27

@PaulLammertsma Doing a layout from bottom won't impact performance either.

@shem 2014-10-13 13:27:45

@RomainGuy weird bug- Integer.MAX_VALUE not work (getView not called) but Integer.MAX_VALUE - 2 work as expected. any idea why?

Related Questions

Sponsored Content

64 Answered Questions

[SOLVED] How do I center text horizontally and vertically in a TextView?

  • 2009-01-11 00:27:55
  • pupeno
  • 1187983 View
  • 1938 Score
  • 64 Answer
  • Tags:   android textview

97 Answered Questions

[SOLVED] Close/hide the Android Soft Keyboard

30 Answered Questions

38 Answered Questions

[SOLVED] How to lazy load of images in ListView in Android

55 Answered Questions

[SOLVED] How do I fix 'android.os.NetworkOnMainThreadException'?

3 Answered Questions

1 Answered Questions

[SOLVED] Slow listview scrolling problem

Sponsored Content