본문 바로가기

안드로이드 ListView 섹션 구분하기 (Using a different View for Separator) 영어주의

Using a different View for Separator

뷰를 넣어 섹션을 구분하기.



The other approach to sectioning is to use an entirely different view for the separator. So let’s create a new layout file at

 /layout/contact_section_header.xml:

<?xml version="1.0" encoding="utf-8"?>

<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"
/>

</LinearLayout>


And this is how our new /layout/contact_item.xml will look like:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp">

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/contact_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/phone_number"
android:textColor="#ff8a8a8c"
android:paddingLeft="8dip"
android:paddingRight="8dip" />


</LinearLayout>



Our onCreate() method will also change to some extent:


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);


mProjection = new String[] {
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
};

final Cursor cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
mProjection,
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
);

int nameIndex = cursor.getColumnIndex(mProjection[0]);
int numberIndex = cursor.getColumnIndex(mProjection[1]);

ArrayList contacts = new ArrayList();

int position = 0;
boolean isSeparator = false;
while(cursor.moveToNext()) {
isSeparator = false;

String name = cursor.getString(nameIndex);
String number = cursor.getString(numberIndex);

char[] nameArray;

// If it is the first item then need a separator
if (position == 0) {
isSeparator = true;
nameArray = name.toCharArray();
}
else {
// Move to previous
cursor.moveToPrevious();

// Get the previous contact's name
String previousName = cursor.getString(nameIndex);

// Convert the previous and current contact names
// into char arrays
char[] previousNameArray = previousName.toCharArray();
nameArray = name.toCharArray();

// Compare the first character of previous and current contact names
if (nameArray[0] != previousNameArray[0]) {
isSeparator = true;
}

// Don't forget to move to next
// which is basically the current item
cursor.moveToNext();
}

// Need a separator? Then create a Contact
// object and save it's name as the section
// header while pass null as the phone number
if (isSeparator) {
Contact contact = new Contact(String.valueOf(nameArray[0]), null, isSeparator);
contacts.add( contact );
}

// Create a Contact object to store the name/number details
Contact contact = new Contact(name, number, false);
contacts.add( contact );

position++;
}

// Creating our custom adapter
CustomAdapter adapter = new CustomAdapter(this, contacts);

// Create the list view and bind the adapter
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
}



So we get the previous contact name and compare its first character with the first character of the current contact name. If they’re different then create a Contact object to which the third argument passed is true while null is passed as the phone number. The name of the contact is set to the first character.

Regardless of the check, one Contact object is always created for the current contact. All these objects are stored in an ArrayList which is then used by our custom adapter.

Let’s quickly look at our Contact class first which is fairly straightforward:


public class Contact {
public String mName;
public String mNumber;
public boolean mIsSeparator;

public Contact(String name, String number, boolean isSeparator) {
mName = name;
mNumber = number;
mIsSeparator = isSeparator;
}

public void setName(String name) {
mName = name;
}

public void setNumber(String number) {
mNumber = number;
}

public void setIsSection(boolean isSection) {
mIsSeparator = isSection;
}
}


The convention is to define getter methods as well, but to keep things short we’ll access the public properties directly from wherever it is required.

Finally, here’s our CustomAdapter class with umpteen new things that we haven’t covered before.

public class CustomAdapter extends BaseAdapter {

private Context mContext;
private ArrayList<Contact> mList;

// View Type for Separators
private static final int ITEM_VIEW_TYPE_SEPARATOR = 0;
// View Type for Regular rows
private static final int ITEM_VIEW_TYPE_REGULAR = 1;
// Types of Views that need to be handled
// -- Separators and Regular rows --
private static final int ITEM_VIEW_TYPE_COUNT = 2;

public CustomAdapter(Context context, ArrayList list) {
mContext = context;
mList = list;
}

@Override
public int getCount() {
return mList.size();
}

@Override
public Object getItem(int position) {
return null;
}

@Override
public long getItemId(int position) {
return 0;
}

@Override
public int getViewTypeCount() {
return ITEM_VIEW_TYPE_COUNT;
}

@Override
public int getItemViewType(int position) {
boolean isSection = mList.get(position).mIsSeparator;

if (isSection) {
return ITEM_VIEW_TYPE_SEPARATOR;
}
else {
return ITEM_VIEW_TYPE_REGULAR;
}
}

@Override
public boolean isEnabled(int position) {
return getItemViewType(position) != ITEM_VIEW_TYPE_SEPARATOR;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

View view;

Contact contact = mList.get(position);
int itemViewType = getItemViewType(position);

if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

if (itemViewType == ITEM_VIEW_TYPE_SEPARATOR) {
// If its a section ?
view = inflater.inflate(R.layout.contact_section_header, null);
}
else {
// Regular row
view = inflater.inflate(R.layout.contact_item, null);
}
}
else {
view = convertView;
}


if (itemViewType == ITEM_VIEW_TYPE_SEPARATOR) {
// If separator

TextView separatorView = (TextView) view.findViewById(R.id.separator);
separatorView.setText(contact.mName);
}
else {
// If regular

// Set contact name and number
TextView contactNameView = (TextView) view.findViewById(R.id.contact_name);
TextView phoneNumberView = (TextView) view.findViewById(R.id.phone_number);

contactNameView.setText( contact.mName );
phoneNumberView.setText( contact.mNumber );
}

return view;
}
}



Three new methods have been introduced (overridden) here:

  • getViewTypeCount() – This method should return the number of types of Views that will be created and returned by the getView() method.
  • getItemViewType() – This method should return an integer representing the type of View that will be created by getView() for the item at the specified position. In our case we’ve defined 2 constants ITEM_VIEW_TYPE_SEPARATOR = 0and ITEM_VIEW_TYPE_REGULAR = 1 that is returned by this method. So basically this method must return and integer between 0 and getViewTypeCount() - 1.
  • isEnabled – If the item at the specified position is a separator (a non-selectable and non-clickable item) then this method should return true, else false.

Finally, if you go through the contents of the getView()method then you’ll notice that we’re basically inflating and setting content (texts) for different layout files and views based on the view type returned by getItemViewType() method.

The best part about this approach is that it is very easy to comprehend and at the same time can manage multiple types of items in the ListView.

Note: Let’s say that our section is something like this – “s1 -> r1, r2, r3 | s2 -> r4, r5” – where sN is a separator and rN is a row, the position of r4 will actually be 6th (5th index) in the list and not 4th (3rd index). Thankfully since we have the data for both separators and rows stored in the ArrayList, when the user taps or does some sort of interaction with the 6th item on the list (r4) the position passed to event listeners will match with that of the data structure (ArrayList).