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.