Android – Sectionned listview
Listviews are commonly used to display a large set of similar data. In some cases, you might need to display your data in groups or sections depending on a certain criteria. This tutorial shows you how to divide your list view’s items into groups with headers in each ‘group’ . There are actually two approaches for us to do that.
But before we start, I would assume you are familiar to list views and list adapters. If not, check out my previous tutorials to get you started.
So, the first approach is about adding a view (communly a textview with the ‘section title’) to your row’s layout file, and switching its visibilty: make it visible if the row is a separator, hide it if not. The second way is to create an entirely different row layout for the seperator. Let us check out together how to implement both methods, and why and when to use each.
#1 Switch visibility of seperator views in each row
As previously mentioned, we will add a text view to the row’s layout. In the bindView() method, we’ll check if the current item belongs to a different group than the previous one and if it is the first item of the entire group or not. If yes, then we simply show up the separator using the setVisibility() method.
Create a layout file and name it movies_list_row.xml , add the following code :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/separator" android:textSize="14sp" android:textStyle="bold" style="?android:listSeparatorTextViewStyle" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/movie_name" android:textSize="18sp" android:layout_marginTop="10dip" android:paddingLeft="8dip" android:paddingRight="8dip" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/movie_rating" android:textColor="#ff8a8a8c" android:paddingLeft="8dip" android:paddingRight="8dip" /> </LinearLayout>
You can notice the seperator textview here, by default it’s visible.
Here is the code for the MainActivity.java class, in which we will add the adapter for our listview.
But beore, dont forget to add your list to the activity_main.xml layout file as follows :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.androidprojects.esprit.sectionnedlistview.MainActivity"> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerInParent="true"/> </LinearLayout>
MainActivity.java class :
public class MainActivity extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new MoviesAdapter(this,null)); setContentView(R.layout.activity_main); } }
Then here is the code for the list’s adapter where we will change the seperator’s visibility whenever we need :
public class MoviesAdapter extends CursorAdapter { /* State of ListView item that has never been determined. */ private static final int STATE_UNKNOWN = 0; /*State of a ListView item that is sectioned. A sectioned item must display the separator.*/ private static final int STATE_SECTIONED_CELL = 1; /* State of a ListView item that is not sectioned and therefore does not display the separator.*/ private static final int STATE_REGULAR_CELL = 2; private final CharArrayBuffer mBuffer = new CharArrayBuffer(128); private int[] mCellStates; public MoviesAdapter(Context context, Cursor cursor) { super(context, cursor); mCellStates = cursor == null ? null : new int[cursor.getCount()]; } @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); mCellStates = cursor == null ? null : new int[cursor.getCount()]; } @Override public void bindView(View view, Context context, Cursor cursor) { final MoviesViewHolder holder = (MoviesViewHolder) view.getTag(); boolean needSeperator = false; final int position = cursor.getPosition(); cursor.copyStringToBuffer(MoviesQuery.TITLE, holder.titleBuffer); switch (mCellStates[position]) { case STATE_SECTIONED_CELL: needSeperator = true; break; case STATE_REGULAR_CELL: needSeperator = false; break; case STATE_UNKNOWN: default: /*A separator is needed if it's the first itemview of the ListView or if the group of the current cell is different from the previous itemview.*/ if (position == 0) { needSeperator = true; } else { cursor.moveToPosition(position - 1); cursor.copyStringToBuffer(MoviesQuery.TITLE, mBuffer); if (mBuffer.sizeCopied > 0 && holder.titleBuffer.sizeCopied > 0 && mBuffer.data[0] != holder.titleBuffer.data[0]) { needSeperator = true; } cursor.moveToPosition(position); } // Cache the result mCellStates[position] = needSeperator ? STATE_SECTIONED_CELL : STATE_REGULAR_CELL; break; } if (needSeperator) { holder.separator.setText(holder.titleBuffer.data, 0, 1); holder.separator.setVisibility(View.VISIBLE); } else { holder.separator.setVisibility(View.GONE); } // title holder.titleView.setText(holder.titleBuffer.data, 0, holder.titleBuffer.sizeCopied); // rating holder.ratingBuffer.setLength(0); final String rating = cursor.getString(MoviesQuery.RATING); if (!TextUtils.isEmpty(rating)) { holder.ratingBuffer.append(rating); } if (TextUtils.isEmpty(holder.ratingBuffer)) { holder.ratingView.setVisibility(View.GONE); } else { holder.ratingView.setVisibility(View.VISIBLE); holder.ratingView.setText(holder.ratingBuffer); } } private interface MoviesQuery { int TITLE = 1; int RATING = 2; } @Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { View v = LayoutInflater.from(context).inflate(R.layout.movies_list_row, viewGroup, false); MoviesViewHolder holder = new MoviesViewHolder(); holder.separator = (TextView) v.findViewById(R.id.separator); holder.titleView = (TextView) v.findViewById(R.id.movie_name); holder.ratingView = (TextView) v.findViewById(R.id.movie_rating); v.setTag(holder); return v; } }
The MoviesViewHolder.java class comes as follows :
public class MoviesViewHolder { public TextView separator; public TextView titleView; public CharArrayBuffer titleBuffer = new CharArrayBuffer(128); public TextView ratingView; public StringBuilder ratingBuffer = new StringBuilder(); }
#2 Create different row layout for seperation
This method consists of creating a totally different row layout for this ‘ seperation row ‘ . So, let us add a movies_seperator_row.xml to our movies_list_row.xml :
movies_seperator_row.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/separatorTxt" android:layout_width="match_parent" android:textSize="80px" android:textStyle="bold" android:layout_height="200px" /> </LinearLayout>
movies_list_row.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/carBrandTxt" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/carRefTxt" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/carSpeedTxt" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
Then, in the MainActivity.java class, we associate our listview to the adapter, where, we inflate either the seperation row layout, or the normal row layout and fill the row view with appropriate data.
MainActivity.java :
public class MainActivity extends ListActivity { private static final Object[] cars = { "Blue cars", new Car("Alfa Romeo",1232,180), new Car("BMW",14972,120), new Car("Audi",14397,160), "Red cars", new Car("Hyundai",15472,180), new Car("Ferrari",159412,120), new Car("BMW",153697,120), }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new CarsAdapter(this,cars) ); setContentView(R.layout.activity_main); }
And here, is the code for our Adapter :
public class CarsAdapter extends BaseAdapter { private static final int ITEM_VIEW_TYPE_Car = 0; private static final int ITEM_VIEW_TYPE_SEPARATOR = 1; private static final int ITEM_VIEW_TYPE_COUNT = 2; private Object[] cars; private ListActivity adaptTo; public CarsAdapter(ListActivity adaptTo,Object[]cars){ this.adaptTo=adaptTo; this.cars=cars; } @Override public int getCount() { return cars.length; } @Override public Object getItem(int i) { return cars[i]; } @Override public long getItemId(int i) { return i; } @Override public int getViewTypeCount() { return ITEM_VIEW_TYPE_COUNT; } @Override public int getItemViewType(int position) { return (cars[position] instanceof String) ? ITEM_VIEW_TYPE_SEPARATOR : ITEM_VIEW_TYPE_Car; } @Override public boolean isEnabled(int position) { // A separator cannot be clicked ! return getItemViewType(position) != ITEM_VIEW_TYPE_SEPARATOR; } @Override public View getView(int i, View view, ViewGroup viewGroup) { final int type = getItemViewType(i); // First, let's create a new convertView if needed. You can also // create a ViewHolder to speed up changes if you want ;) if (view == null) { view = LayoutInflater.from(adaptTo.getBaseContext()).inflate( type == ITEM_VIEW_TYPE_SEPARATOR ? R.layout.movies_seperator_row : R.layout.movies_list_row, viewGroup, false); } // We can now fill the list item view with the appropriate data. if (type == ITEM_VIEW_TYPE_SEPARATOR) { ((TextView) view.findViewById(R.id.separatorTxt)).setText((String) getItem(i)); } else { final Car car = (Car) getItem(i); ((TextView) view.findViewById(R.id.carBrandTxt)).setText(car.getBrand()); ((TextView) view.findViewById(R.id.carRefTxt)).setText(car.getRef()); ((TextView) view.findViewById(R.id.carRefTxt)).setText(car.getRef()); } return view; } }
Here is the source code, but dont download it !
That’s all coders ! I hope this helped you understand how to section your lists, and I hope you try it out without the need to check the source code provided here. It’s not that hard a task still ! So happy coding, and let me know if you need any help.
Recent Comments