Monday, July 2, 2012

Section Adapter

I found an interested post about SectionAdapter by Jeff Sharkey. I will insist that it is the better way of creating SectionAdapter in the way that Jeff has opted here in his blog.

The blog just says to use seperate Adapter for every Header, so that we can customize it easily in the best possible way. The idea is very much clear and interesting. So, we can customize it and create as required by us. Below is how we can customize the SectionAdapter.

SectionedAdapter.java

abstract public class SectionedAdapter extends BaseAdapter {

    
    String TAG = getClass().getSimpleName();
    
    abstract protected View getHeaderView(String caption, int index, View convertView, ViewGroup parent);
    
    private List<Section> sections = new ArrayList<Section>();
    private static int TYPE_SECTION_HEADER = 0;
    
    public SectionedAdapter() {
        super();
        sections.clear();
    }
    
    public void addSection(String caption, Adapter adapter) {
        sections.add(new Section(caption, adapter));
    }
    
    public void clear() {
        sections.clear();
        notifyDataSetChanged();
    }
    
    public Object getItem(int position) {
        for (Section section : this.sections) {
            if (position == 0) {
                return (section);
            }
            
            int size = section.adapter.getCount() + 1;
            
            if (position < size) {
                return (section.adapter.getItem(position - 1));
            }
            position -= size;
        }
        return (null);
    }
    
    public int getCount() {
        int total = 0;
        
        for (Section section : this.sections) {
            total += section.adapter.getCount() + 1; // add one for header
        }
        return (total);
    }
    
    public int getViewTypeCount() {
        int total = 1; // one for the header, plus those from sections
        
        for (Section section : this.sections) {
            total += section.adapter.getViewTypeCount();
        }
        return (total);
    }
    
    public int getItemViewType(int position) {
        int typeOffset = TYPE_SECTION_HEADER + 1; // start counting from here
        
        for (Section section : this.sections) {
            if (position == 0) {
                return (TYPE_SECTION_HEADER);
            }
            
            int size = section.adapter.getCount() + 1;
            
            if (position < size) {
                int value = (typeOffset + section.adapter
                .getItemViewType(position - 1));
                return value;
            }
            
            position -= size;
            typeOffset += section.adapter.getViewTypeCount();
        }
        return (-1);
    }
    
    public boolean areAllItemsSelectable() {
        return (false);
    }
    
    public boolean isEnabled(int position) {
        return (getItemViewType(position) != TYPE_SECTION_HEADER);
    }
    
    public View getView(int position, View convertView, ViewGroup parent) {
        int sectionIndex = 0;
        
        for (Section section : this.sections) {
            if (position == 0) {
                return (getHeaderView(section.caption, sectionIndex, convertView, parent));
            }
            
            int size = section.adapter.getCount() + 1;
            
            if (position < size) {
                return (section.adapter.getView(position - 1, convertView, parent));
            }
            
            position -= size;
            sectionIndex++;
        }
        return (null);
    }
    
    public long getItemId(int position) {
        return (position);
    }
    
    class Section {
        String caption = null;
        Adapter adapter = null;
        
        Section(String caption, Adapter adapter) {
            this.caption = caption;
            this.adapter = adapter;
        }
    }
}

Above class is abstract and having an abstract method 
getHeaderView(String caption, int index, View convertView, ViewGroup parent) that is because we can easily customize the Header Section also dynamically from our Activity. In Above class we have an inner class Section that does the work of maintaining every Header having its own Adapter. So, the class is nicely set-up to be used.
So, now we will create an Activity having a ListView,


MainActivity.java

public class MainActivity extends Activity {

    
    ListView mListView;
    ArrayList<String> mArrayList = new ArrayList<String>();
    SectionedAdapter adapter;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mListView = (ListView) findViewById(R.id.listview);
        
        adapter = new SectionedAdapter() {
            
            @Override
            protected View getHeaderView(String caption, int index,
            View convertView, ViewGroup parent) {
                convertView = getLayoutInflater().inflate(R.layout.section_header, null);
                TextView header = (TextView) convertView.findViewById(R.id.header);
                header.setText(caption);
                return convertView;
            }
        };
        
        for (int i = 0; i < 5; i++) {
            mArrayList.add("Item " + i);
            MyAdapter myAdapter = new MyAdapter();
            adapter.addSection("Header " + i, myAdapter);
        }
        mListView.setOnItemClickListener(new OnItemClickListener() {
            
            public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
                Toast.makeText(getApplicationContext(), arg0.getAdapter().getItem(position).toString(), Toast.LENGTH_LONG).show();
            }
        });
        mListView.setAdapter(adapter);
    }
    
    class MyAdapter extends BaseAdapter {
        
        public int getCount() {
            return mArrayList.size();
        }
        
        public Object getItem(int position) {
            return mArrayList.get(position);
        }
        
        public long getItemId(int position) {
            return position;
        }
        
        public View getView(int position, View convertView, ViewGroup parent) {
            convertView = (TextView) getLayoutInflater().inflate(R.layout.section_item, null);
            TextView item = (TextView) convertView.findViewById(R.id.item);
            item.setText(mArrayList.get(position));
            return convertView;
        }
    }
}

So, what we have in the Activity class is an instance of 
SectionedAdapter adapter; class with override method getHeaderView(String caption, int index, View convertView, ViewGroup parent)  by which we can inflate any xml and create our own custom Header. Also I had created an inner class MyAdapter that extends BaseAdapter which will be used as an Adapter for each Header. To add an Seperator as Header we can use addSection(String Caption, Adapter adapter) method to add the Header caption and a seperate Adapter related to that Header. In this way you can create seperate Adapter for each Header and link to it. I am attaching Demo source code for the same. Enjoy!!!!









1 comment:

  1. This code seems to work alright, but I've found that if I use multiple sections, then whatever I assign to the second section overtakes what is in the first section. I'd be happy to post an example of this somewhere, but you can probably see what I'm talking about. I'd love to know how to work around this.

    ReplyDelete