Here we will have a very simple tutorial for Custom ListView in android with Section Header.
We will able to display final view as per below images
Steps to Create Custom List View
- Open Eclipse to Create New Project.
- Create New Project with package name com.sks.demo.custom_list
- Add following images in reg->drawable folder
- Create a new List Section Header Layout (find the below item_composer_header.xml file)
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_section_header" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/list_header_bg" android:padding="4dp" android:paddingLeft="10dp" android:shadowColor="#000" android:shadowDx="1" android:shadowDy="1" android:shadowRadius="1" android:text="Header" android:textColor="#FFF" android:textSize="16sp" android:textStyle="bold" android:typeface="serif" />
- Create a new List Item Custom Layout (find the below item_composer.xml file)
-
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <include android:layout_width="fill_parent" android:layout_height="wrap_content" layout="@layout/item_composer_header" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/custom_list_item_bg" android:orientation="vertical" android:paddingBottom="3dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="3dp" > <TextView android:id="@+id/list_section_title1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Full Name" android:textColor="@color/white" android:textStyle="bold" /> <TextView android:id="@+id/list_section_title2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="1500-1600" android:textColor="@color/white" /> </LinearLayout> </LinearLayout>
- Now Edit your main.xml to set Custom ListView
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/background" > <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="State wise Cities in India" /> <com.sks.demo.custom_list.list.SKSCustomListView android:id="@+id/list_services" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@+id/title" android:background="#00ffffff" android:cacheColorHint="#00000000" android:divider="@drawable/img_list_seperator" android:dividerHeight="1dip" android:drawingCacheQuality="high" android:keepScreenOn="false" > </com.sks.demo.custom_list.list.SKSCustomListView> </RelativeLayout>
- Here we are going to create our custom list view Widget.
- We need to set Some Data & Composer classes for list view item
- Following is the Data & Composer Class in Default Package
Code : Data.java
package com.sks.demo.custom_list; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.os.SystemClock; import android.util.Pair; public class Data { public static List<Pair<String, List<Composer>>> getAllData() { List<Pair<String, List<Composer>>> res = new ArrayList<Pair<String, List<Composer>>>(); for (int i = 0; i < 4; i++) { res.add(getOneSection(i)); } return res; } public static List<Composer> getFlattenedData() { List<Composer> res = new ArrayList<Composer>(); for (int i = 0; i < 4; i++) { res.addAll(getOneSection(i).second); } return res; } public static Pair<Boolean, List<Composer>> getRows(int page) { List<Composer> flattenedData = getFlattenedData(); if (page == 1) { return new Pair<Boolean, List<Composer>>(true, flattenedData.subList(0, 5)); } else { SystemClock.sleep(2000); // simulate loading return new Pair<Boolean, List<Composer>>(page * 5 < flattenedData.size(), flattenedData.subList((page) * 5, Math.min(page * 5, flattenedData.size()))); } } public static Pair<String, List<Composer>> getOneSection(int index) { String[] titles = { "Maharashtra", "Gujarat", "Jammu", "Punjab" }; Composer[][] composerss = { { new Composer("Amravati", "2,607,160"), new Composer("Aurangabad", "2,897,103"), new Composer("Khandala", "21,043"), new Composer("Mumbai", "11,914,398"), new Composer("Nagpur", "2,420,000"), new Composer("Pune", "4,485,000"), }, { new Composer("Ahmedabad", "3,913,793"), new Composer("Surat", "3,344,135"), new Composer("Vadodara", "1,513,758"), new Composer("Rajkot", "1,395,026"), new Composer("Gandhinagar", "271,331"), new Composer("Bhavnagar", "600,594"), }, { new Composer("Kathua", "550,084"), new Composer("Jammu", "1,343,756"), new Composer("Samba", "245,016"), new Composer("Udhampur", "475,068"), new Composer("Reasi", "268,441"), new Composer("Rajouri", "483,284"), }, { new Composer("Amritsar", "1,183,705"), new Composer("Firozpur", "110,091"), new Composer("Ludhiana", "1,613,878"), new Composer("Chandigarh", "27,704,236"), new Composer("Jalandhar ", "873,725"), new Composer("Patiala", "445,196"), }, }; return new Pair<String, List<Composer>>(titles[index], Arrays.asList(composerss[index])); } }
Code : Composer.javapackage com.sks.demo.custom_list; public class Composer { public static final String TAG = Composer.class.getSimpleName(); public String name; public String year; public Composer(String name, String year) { this.name = name; this.year = year; } }
- Now Create Custom List & Adapter
- Create New Package com.sks.demo.custom_list.list
- Create new class SKSCustomListAdapter.java
- Write code for SKSCustomListAdapter.java
package com.sks.demo.custom_list.list; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.SectionIndexer; public abstract class SKSCustomListAdapter extends BaseAdapter implements SectionIndexer, OnScrollListener { public static final String TAG = SKSCustomListAdapter.class.getSimpleName(); public interface HasMorePagesListener { void noMorePages(); void mayHaveMorePages(); } /** * The <em>current</em> page, not the page that is going to be loaded. */ int page = 1; int initialPage = 1; boolean automaticNextPageLoading = false; HasMorePagesListener hasMorePagesListener; void setHasMorePagesListener(HasMorePagesListener hasMorePagesListener) { this.hasMorePagesListener = hasMorePagesListener; } /** * Pinned header state: don't show the header. */ public static final int PINNED_HEADER_GONE = 0; /** * Pinned header state: show the header at the top of the list. */ public static final int PINNED_HEADER_VISIBLE = 1; /** * Pinned header state: show the header. If the header extends beyond * the bottom of the first shown element, push it up and clip. */ public static final int PINNED_HEADER_PUSHED_UP = 2; /** * Computes the desired state of the pinned header for the given * position of the first visible list item. Allowed return values are * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or * {@link #PINNED_HEADER_PUSHED_UP}. */ public int getPinnedHeaderState(int position) { if (position < 0 || getCount() == 0) { return PINNED_HEADER_GONE; } // The header should get pushed up if the top item shown // is the last item in a section for a particular letter. int section = getSectionForPosition(position); int nextSectionPosition = getPositionForSection(section + 1); if (nextSectionPosition != -1 && position == nextSectionPosition - 1) { return PINNED_HEADER_PUSHED_UP; } return PINNED_HEADER_VISIBLE; } /** * Sets the initial page when {@link #resetPage()} is called. * Default is 1 (for APIs with 1-based page number). */ public void setInitialPage(int initialPage) { this.initialPage = initialPage; } /** * Resets the current page to the page specified in {@link #setInitialPage(int)}. */ public void resetPage() { this.page = this.initialPage; } /** * Increases the current page number. */ public void nextPage() { this.page++; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (view instanceof SKSCustomListView) { ((SKSCustomListView) view).configureHeaderView(firstVisibleItem); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // nop } @Override public final View getView(int position, View convertView, ViewGroup parent) { View res = getAmazingView(position, convertView, parent); if (position == getCount() - 1 && automaticNextPageLoading) { onNextPageRequested(page + 1); } final int section = getSectionForPosition(position); boolean displaySectionHeaders = (getPositionForSection(section) == position); bindSectionHeader(res, position, displaySectionHeaders); return res; } public void notifyNoMorePages() { automaticNextPageLoading = false; if (hasMorePagesListener != null) hasMorePagesListener.noMorePages(); } public void notifyMayHaveMorePages() { automaticNextPageLoading = true; if (hasMorePagesListener != null) hasMorePagesListener.mayHaveMorePages(); } /** * The last item on the list is requested to be seen, so do the request * and call {@link SKSCustomListView#tellNoMoreData()} if there is no more pages. * * @param page the page number to load. */ protected abstract void onNextPageRequested(int page); /** * Configure the view (a listview item) to display headers or not based on displaySectionHeader * (e.g. if displaySectionHeader header.setVisibility(VISIBLE) else header.setVisibility(GONE)). */ protected abstract void bindSectionHeader(View view, int position, boolean displaySectionHeader); /** * read: get view too */ public abstract View getAmazingView(int position, View convertView, ViewGroup parent); /** * Configures the pinned header view to match the first visible list item. * * @param header pinned header view. * @param position position of the first visible list item. * @param alpha fading of the header view, between 0 and 255. */ public abstract void configurePinnedHeader(View header, int position, int alpha); @Override public abstract int getPositionForSection(int section); @Override public abstract int getSectionForPosition(int position); @Override public abstract Object[] getSections(); }
- Create new Class SKSCustomListView.java
- Write code for SKSCustomListView.java
package com.sks.demo.custom_list.list; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; import com.sks.demo.custom_list.list.SKSCustomListAdapter.HasMorePagesListener; /** * A ListView that maintains a header pinned at the top of the list. The pinned * header can be pushed up and dissolved as needed. * * It also supports pagination by setting a custom view as the loading * indicator. */ public class SKSCustomListView extends ListView implements HasMorePagesListener { View listFooter; boolean footerViewAttached = false; private View mHeaderView; private boolean mHeaderViewVisible; private int mHeaderViewWidth; private int mHeaderViewHeight; private SKSCustomListAdapter adapter; public void setPinnedHeaderView(View view) { mHeaderView = view; // Disable vertical fading when the pinned header is present // TODO change ListView to allow separate measures for top and bottom // fading edge; // in this particular case we would like to disable the top, but not the // bottom edge. if (mHeaderView != null) { setFadingEdgeLength(0); } requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHeaderView != null) { measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); mHeaderViewWidth = mHeaderView.getMeasuredWidth(); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mHeaderView != null) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); configureHeaderView(getFirstVisiblePosition()); } } public void configureHeaderView(int position) { if (mHeaderView == null) { return; } int state = adapter.getPinnedHeaderState(position); switch (state) { case SKSCustomListAdapter.PINNED_HEADER_GONE: { mHeaderViewVisible = false; break; } case SKSCustomListAdapter.PINNED_HEADER_VISIBLE: { adapter.configurePinnedHeader(mHeaderView, position, 255); if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } mHeaderViewVisible = true; break; } case SKSCustomListAdapter.PINNED_HEADER_PUSHED_UP: { View firstView = getChildAt(0); if (firstView != null) { int bottom = firstView.getBottom(); int headerHeight = mHeaderView.getHeight(); int y; int alpha; if (bottom < headerHeight) { y = (bottom - headerHeight); alpha = 255 * (headerHeight + y) / headerHeight; } else { y = 0; alpha = 255; } adapter.configurePinnedHeader(mHeaderView, position, alpha); if (mHeaderView.getTop() != y) { mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); } mHeaderViewVisible = true; } break; } } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mHeaderViewVisible) { drawChild(canvas, mHeaderView, getDrawingTime()); } } public SKSCustomListView(Context context) { super(context); } public SKSCustomListView(Context context, AttributeSet attrs) { super(context, attrs); } public SKSCustomListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setLoadingView(View listFooter) { this.listFooter = listFooter; } public View getLoadingView() { return listFooter; } @Override public void setAdapter(ListAdapter adapter) { if (!(adapter instanceof SKSCustomListAdapter)) { throw new IllegalArgumentException(SKSCustomListView.class.getSimpleName() + " must use adapter of type " + SKSCustomListAdapter.class.getSimpleName()); } // previous adapter if (this.adapter != null) { this.adapter.setHasMorePagesListener(null); this.setOnScrollListener(null); } this.adapter = (SKSCustomListAdapter) adapter; ((SKSCustomListAdapter) adapter).setHasMorePagesListener(this); this.setOnScrollListener((SKSCustomListAdapter) adapter); View dummy = new View(getContext()); super.addFooterView(dummy); super.setAdapter(adapter); super.removeFooterView(dummy); } @Override public SKSCustomListAdapter getAdapter() { return adapter; } @Override public void noMorePages() { if (listFooter != null) { this.removeFooterView(listFooter); } footerViewAttached = false; } @Override public void mayHaveMorePages() { if (!footerViewAttached && listFooter != null) { this.addFooterView(listFooter); footerViewAttached = true; } } public boolean isLoadingViewVisible() { return footerViewAttached; } }
- Finally We reach to create our Start-up Activity named as CustomListDemoActivity.java
- In main package i.e. com.sks.demo.custom_list Create new Activity CustomListDemoActivity.java
- Write code for CustomListDemoActivity.java
Code : CustomListDemoActivity.java
package com.sks.demo.custom_list; import java.util.List; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView; import com.sks.demo.custom_list.list.SKSCustomListAdapter; import com.sks.demo.custom_list.list.SKSCustomListView; public class CustomListDemoActivity extends Activity { SKSCustomListView lsComposer; SectionComposerAdapter adapter; Context context; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); context = getParent(); } @Override protected void onStart() { super.onStart(); initializedView(); setupFunctionality(); setupListeners(); } private void initializedView() { lsComposer = (SKSCustomListView) findViewById(R.id.list_services); } private void setupFunctionality() { lsComposer.setPinnedHeaderView(LayoutInflater.from(this).inflate(R.layout.item_composer_header, lsComposer, false)); lsComposer.setAdapter(adapter = new SectionComposerAdapter()); lsComposer.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Intent intent = new Intent(CustomListDemoActivity.this, // ServiceDestailsScreen.class); String name, address, title; title = ((TextView) view.findViewById(R.id.list_section_header)).getText().toString(); name = ((TextView) view.findViewById(R.id.list_section_title1)).getText().toString(); address = ((TextView) view.findViewById(R.id.list_section_title2)).getText().toString(); // intent.putExtra("serviceName", title); // intent.putExtra("name", name); // intent.putExtra("address", address); Log.v(Util.TAG, "Name : " + name); Log.v(Util.TAG, "Address : " + address); Log.v(Util.TAG, "Title: " + title); // getParent().startActivity(intent); } }); } private void setupListeners() { // titleBack.setOnClickListener(this); } class SectionComposerAdapter extends SKSCustomListAdapter { List<Pair<String, List<Composer>>> all = Data.getAllData(); @Override public int getCount() { int res = 0; for (int i = 0; i < all.size(); i++) { res += all.get(i).second.size(); } return res; } @Override public Composer getItem(int position) { int c = 0; for (int i = 0; i < all.size(); i++) { if (position >= c && position < c + all.get(i).second.size()) { return all.get(i).second.get(position - c); } c += all.get(i).second.size(); } return null; } @Override public long getItemId(int position) { return position; } @Override protected void onNextPageRequested(int page) { } @Override protected void bindSectionHeader(View view, int position, boolean displaySectionHeader) { if (displaySectionHeader) { view.findViewById(R.id.list_section_header).setVisibility(View.VISIBLE); TextView lSectionTitle = (TextView) view.findViewById(R.id.list_section_header); lSectionTitle.setText(getSections()[getSectionForPosition(position)]); } else { TextView lSectionTitle = (TextView) view.findViewById(R.id.list_section_header); lSectionTitle.setText(getSections()[getSectionForPosition(position)]); view.findViewById(R.id.list_section_header).setVisibility(View.GONE); } } @Override public View getAmazingView(int position, View convertView, ViewGroup parent) { View res = convertView; if (res == null) res = getLayoutInflater().inflate(R.layout.item_composer, null); TextView lName = (TextView) res.findViewById(R.id.list_section_title1); TextView lYear = (TextView) res.findViewById(R.id.list_section_title2); Composer composer = getItem(position); lName.setText(composer.name); lYear.setText("Population : " + composer.year); return res; } @Override public void configurePinnedHeader(View header, int position, int alpha) { TextView lSectionHeader = (TextView) header; lSectionHeader.setText(getSections()[getSectionForPosition(position)]); // lSectionHeader.setBackgroundColor(alpha << 24 | (0xbbffbb)); // lSectionHeader.setTextColor(alpha << 24 | (0x000000)); } @Override public int getPositionForSection(int section) { if (section < 0) section = 0; if (section >= all.size()) section = all.size() - 1; int c = 0; for (int i = 0; i < all.size(); i++) { if (section == i) { return c; } c += all.get(i).second.size(); } return 0; } @Override public int getSectionForPosition(int position) { int c = 0; for (int i = 0; i < all.size(); i++) { if (position >= c && position < c + all.get(i).second.size()) { return i; } c += all.get(i).second.size(); } return -1; } @Override public String[] getSections() { String[] res = new String[all.size()]; for (int i = 0; i < all.size(); i++) { res[i] = all.get(i).first; } return res; } } }
- Make sure the changes in AndroidMenifest.xml
Code : AndroidMenifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sks.demo.custom_list" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".CustomListDemoActivity" 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>
- Finally Code writing work has been complete. Start compiling Android project.
- Run the project.
- Result will be like below
SVN Repository
Enjoy the Code and do not forget to leave comments.
15 comments:
The SVN Link is down :(
Hi,
Its Working Now... Please Check ... :)
hi in this pinned listview when we scroll down to bottom of the listview both the section are visible how to remove that one
hello,
basically the purpose of this ListView is as given in screenshot.
have a look of iPhone contact list, if you scroll up or down it will show you section header of hidden item also.
if you need to use non pinned header then refer below link
http://developer.android.com/reference/android/widget/SectionIndexer.html
hi thanks for the replay in your data.class you have defined titles and composerss with fixed sizes how can i define dynamic those two things. i have used arraylist in place of array but it is not generating the list view. so how those two should be initialized dynamically?
This is a nice question...
Well if we talk about Composer.java
It is for Holding data at run-time like customization.
now if we talk about Data.java, here is the main challenge.
till now there is no constructor for Data.java
so you can create the constructor with argument List<Pair<String, List<Composer>>>
For Ex.
public Data(List<Pair<String, List<Composer>>> myListContaint)
{
......
}
now change the static value of for loop to dynamic by getting myListContaint size or count;
if possible remove the static keyword of every method from Data.java and make changes in code accordingly.
It should work.
Let me know if you face any difficulties.
Hi... am getting an error in CustomListActivity.java in main package at (Log.v(util.TAG, + "Name : =" + name) and
(Log.v(util.TAG, + "Title: =" + title) and
(Log.v(util.TAG, + "Address: =" + address)....
I'm getting an error as
"util cannot be resolved to a variable".. please help me.
Hi,
just replace util.TAG by any string object. for Ex. "CustomList"
it should work. or checkout project from SVN repository
Thanks . Code really helped me
i want to use this custom list with database can u plz help me where i should change in which file. i have dummy data set with me.thnx
Hi Jitendra Kumar,
Just check comment on below link, you will get help
http://smartphonebysachin.blogspot.in/2012/03/custom-listview-with-separator-and.html?showComment=1335949651533#c5341016054674069826
Hi,thanks for such a nice piece of code.
I have a confusion over here.
I have total 3 Array of String to display its content in "list_section_header","list_section_title1","list_section_title2".
Can you please guide me to modify the code ?
I would be really grateful if you can guide me towards my target.
:-)
Hi Sachin, you very good example, excuse my English, but I need your help, at the end of each section, I need to add an item "More data" (footer), in order to show more results, as could do that. I would greatly appreciate you can help.
Regards, Roberto
Hello Can you Please tell me how do i add 2 arraylist into this. I need 2 arraylist in my example here you have one list and String, how do i add another list or arraylist insted of String??? Please Help!!!!
Thank you
Post a Comment