Lets begin with the CheckableRelativeLayout.
src/CheckableRelativeLayout.java
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import android.widget.RelativeLayout;
/**
* Extension of a relative layout to provide a checkable behaviour
*
* @author marvinlabs
*/
public class CheckableRelativeLayout extends RelativeLayout implements
Checkable {
private boolean isChecked;
private List<Checkable> checkableViews;
public CheckableRelativeLayout(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initialise(attrs);
}
public CheckableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialise(attrs);
}
public CheckableRelativeLayout(Context context, int checkableId) {
super(context);
initialise(null);
}
/*
* @see android.widget.Checkable#isChecked()
*/
public boolean isChecked() {
return isChecked;
}
/*
* @see android.widget.Checkable#setChecked(boolean)
*/
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
for (Checkable c : checkableViews) {
c.setChecked(isChecked);
}
}
/*
* @see android.widget.Checkable#toggle()
*/
public void toggle() {
this.isChecked = !this.isChecked;
for (Checkable c : checkableViews) {
c.toggle();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int childCount = this.getChildCount();
for (int i = 0; i < childCount; ++i) {
findCheckableChildren(this.getChildAt(i));
}
}
/**
* Read the custom XML attributes
*/
private void initialise(AttributeSet attrs) {
this.isChecked = false;
this.checkableViews = new ArrayList<Checkable>(5);
}
/**
* Add to our checkable list all the children of the view that implement the
* interface Checkable
*/
private void findCheckableChildren(View v) {
if (v instanceof Checkable) {
this.checkableViews.add((Checkable) v);
}
if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
final int childCount = vg.getChildCount();
for (int i = 0; i < childCount; ++i) {
findCheckableChildren(vg.getChildAt(i));
}
}
}
}
After that just use it in your XML layout files, make it part of your package and use syntax <org.mypackage.myapp.CheckableRelativeLayout>
Our testclass Team, which we want to populate the ListView with.
src/Team.java
public class Team {
private String teamName;
private int teamWins;
public Team(String name, int wins)
{
teamName = name;
teamWins = wins;
}
public String getTeamName() {
return teamName;
}
public int getTeamWins() {
return teamWins;
}
}
Simple class for testing purposes, next up we build our ListView layout, what each row will look like.
I decided on a CheckBox (the new one as the old one felt big), and two TextViews to display the teamname & team wins. (Remember to look at the CheckableRelativeLayout class above as this isn't working with the normal containers.)
layout/row_team_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<se.adanware.listviewexample.CheckableRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- We dont want to be able to click the CheckBox -
android:clickable="false" added.
CheckableRelativeLayout takes care of the toggle when clicking the row -->
<CheckBox
android:id="@+id/myCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:layout_alignParentLeft="true"
android:background="@drawable/customcheckbox_background"
android:button="@drawable/customcheckbox"
/>
<TextView
android:id="@+id/listview_TeamDescription"
android:focusable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:layout_toRightOf="@id/myCheckBox"
android:layout_centerVertical="true"
/>
<TextView
android:id="@+id/listview_TeamWins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:paddingRight="5dp"
android:layout_centerVertical="true"
android:focusable="false"
android:layout_alignParentRight="true"
/>
</se.adanware.listviewexample.CheckableRelativeLayout>
I'm still targeting the earlier Android version so i've taken the new checkbox from the SDK.
drawable-hdpi/customcheckbox.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false"
android:drawable="@drawable/btn_check_off_holo_dark" />
<item android:state_checked="true"
android:drawable="@drawable/btn_check_on_holo_dark" />
</selector>
drawable-hdp/customcheckbox_background.xml<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/btn_check_label_background" />
</selector>
![]() |
| btn_check_label_background.9.png |
![]() |
| btn_check_on_holo_dark.png |
![]() |
| btn_check_off_holo_dark.png |
src/TeamListViewAdapter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class TeamListViewAdapter extends ArrayAdapter<Team>
{
View row;
ArrayList<Team> myTeams;
int resLayout;
Context context;
public TeamListViewAdapter(Context context, int textViewResourceId, ArrayList<Team> myTeams) {
super(context, textViewResourceId, myTeams);
this.myTeams = myTeams;
resLayout = textViewResourceId;
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
row = convertView;
if(row == null)
{ // inflate our custom layout. resLayout == R.layout.row_team_layout.xml
LayoutInflater ll = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = ll.inflate(resLayout, parent, false);
}
Team item = myTeams.get(position); // Produce a row for each Team.
if(item != null)
{ // Find our widgets and populate them with the Team data.
TextView myTeamDescription = (TextView) row.findViewById(R.id.listview_TeamDe scription);
TextView myTeamWins = (TextView) row.findViewById(R.id.listview_TeamWins);
if(myTeamDescription != null)
myTeamDescription.setText(item.getTeamName());
if(myTeamWins != null)
myTeamWins.setText("Wins: " + String.valueOf(item.getTeamWins()));
}
return row;
}
}
Okay! Now we just need to make the root layout and our start activity! First a simple layout:
res/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Checkable Listview Example"
/>
<Button android:id="@+id/buttonStart"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="Start tournament with selected teams"
/>
<ListView android:id="@+id/myListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:choiceMode="multipleChoice"/>
</LinearLayout>
Nothing unusual here, android:choiceMode="multipleChoice" cant be omitted.
It can also be set from code with myListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
There's also android:choiceMode="singleChoice" if you just want a single item checked.
If not set the ListView.getCheckedItemPositions() method will return a null SparseBooleanArray.
Finally we come to our startup activity!
src/ListViewExample.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.*;
import java.util.ArrayList;
public class ListViewExample extends Activity
{
ArrayList<Team> myTeams;
TeamListViewAdapter myAdapter;
ListView myListView;
Button myButton;
@Override
public void onCreate(Bundle savedInstanceState)
{
myTeams = new ArrayList<Team>();
// Add a few teams to display.
myTeams.add(new Team("Winners", 10));
myTeams.add(new Team("Philidelphia Flyers", 5));
myTeams.add(new Team("Detroit Red Wings", 1));
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myListView = (ListView) findViewById(R.id.myListView);
myButton = (Button) findViewById(R.id.buttonStart);
// Construct our adapter, using our own layout and myTeams
myAdapter = new TeamListViewAdapter(this, R.layout.row_team_layout, myTeams );
myListView.setAdapter(myAdapter);
myListView.setItemsCanFocus(false);
myButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ArrayList<Team> selectedTeams = new ArrayList<Team>();
final SparseBooleanArray checkedItems = myListView.getCheckedItemPositions();
int checkedItemsCount = checkedItems.size();
for (int i = 0; i < checkedItemsCount; ++i) {
// Item position in adapter
int position = checkedItems.keyAt(i);
// Add team if item is checked == TRUE!
if(checkedItems.valueAt(i))
selectedTeams.add(myAdapter.getItem(position));
}
if(selectedTeams.size() < 2)
Toast.makeText(getBaseContext(), "Need to select two or more teams.", Toast .LENGTH_SHORT).show();
else
{
// Just logging the output.
for(Team t : selectedTeams)
Log.d("SELECTED TEAMS: ", t.getTeamName());
}
}
});
}
}
Thanks goes to http://www.marvinlabs.com/2010/10/custom-listview-ability-check-items/ for the elegant solution!
... Family time, nightie!



