Search code examples
androidscrollandroid-arrayadaptercontactssectionindexer

SectionIndexer shifted in mock contacts app


EDIT: Solution at bottom of post.

I am creating a contact screen for an android app where I want to be able to scroll through my phones contacts. I am able to import the phones contacts, use a custom arrayadapter to organize my information, and enable the fastscroll option for the listview. In addition, I am able to get the first letter of the current section to appear next to my thumb when I scroll. (i.e. if I am in the area where the names start with an A, it should show 'A' in the bubble next to my thumb which is scrolling).

My problem is that instead of showing the correct letter based on the current location, it shows 1 letter BEHIND where I am. For example, I am in the B section of contacts and it shows the letter 'A'. Any thoughts of what I am doing wrong?

Here is my adapter:

public class ContactAdapter extends ArrayAdapter<Contact> implements SectionIndexer {
    Context context;
    int layoutResourceId;
    ArrayList<Contact> contacts = null;

    private HashMap<String, Integer> alphaIndexer;
    private String[] sections;

    public ContactAdapter(Context context, int layoutResourceId, ArrayList<Contact> contacts) {
        super(context, layoutResourceId, contacts);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.contacts = contacts;

        Collections.sort(this.contacts);

        alphaIndexer = new HashMap<String, Integer>();
        for (int x = 0; x < this.contacts.size(); x++)
            alphaIndexer.put(this.contacts.get(x).getSection(), x);
        Set<String> sectionLetters = alphaIndexer.keySet();
        ArrayList<String> sectionList = new ArrayList<String>(sectionLetters);
        Collections.sort(sectionList);
        sections = new String[sectionList.size()];
        sectionList.toArray(sections);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Additional code here
    }

    static class ContactHolder {
        TextView txtName;
        CheckBox checked;
    }

    public int getPositionForSection(int section) {
        return alphaIndexer.get(sections[section]);
    }

    public int getSectionForPosition(int position) {
        return 0;
    }

    public Object[] getSections() {
        return sections;
    }
}

Here is the code that ended up making this work:

public ContactAdapter(Context context, int layoutResourceId, ArrayList<Contact> contacts) {
    super(context, layoutResourceId, contacts);
    this.layoutResourceId = layoutResourceId;
    this.context = context;
    this.contacts = contacts;
    this.results = new ArrayList<Contact>();

    Collections.sort(this.contacts);

    // Keys are the different sections, values are the indexes at which those sections start
    alphaIndexer = new HashMap<String, Integer>();

    // Only set the value for each key (section) when we encounter a new section
    String prevSection = "";
    for (int x = 0; x < this.contacts.size(); x++) {
        String section = this.contacts.get(x).getSection();
        if (!prevSection.equals(section)) {
            alphaIndexer.put(section, x);
            prevSection = section;
        }
    }

    Set<String> sectionLetters = alphaIndexer.keySet();
    ArrayList<String> sectionList = new ArrayList<String>(sectionLetters);
    Collections.sort(sectionList);
    sections = new String[sectionList.size()];
    sectionList.toArray(sections);
}

Solution

  • Your implementation looks okay as far as I can see. I think there are two possibilities:

    1. Your Contact.getSection() method doesn't return the right value.
    2. Actually your indexer works correctly, but it just seems working oddly to you. If the item at the very top of your screen (not of your list) matches your section index, it works correctly, since it's always the very top item that determines your actual position in the list. So if the indexer shows the letter 'B' and the first contact visible on your screen starts with a 'B', it's correct (although most of the currently visible contacts may start with a 'C', which may lead to the erroneous assumption of being in section 'C').

    If you can assure both things to be correct, please provide a screenshot of the error and a print of your alphaIndexer mapping.


    Although not directly related to your question, you should rework your implementation of getSectionForPosition() (sic!), since returning a constant may lead to an incorrect positioning of the scrollbar when swiping through your list (see here for further information).