The first thought one might have is to customize several features of the Activity List itself. However, this would take quite some time and would be a huge effort in order to accomplish a simple thing: select an item.
Another solution, which is quite a good one, is to, instead of adding a CheckBox component, add an Image component for a check box. This image would hold two figures: one for a selected check box and another for a unselected check box. They would be changed whenever this image was clicked. Therefore, one would have the behavior of the check box implemented and the list would behave naturally, allowing items selection.
A third solution, and the simplest one, would be to use the CheckedTextView component. This is actually a check box with text component. One simply omits the text by providing none. This component behaves exactly like a CheckBox with the advantage of allowing the Activity List to behave naturally, allowing items selection. Thus, a substitution of a CheckBox component for a CheckedTextView would be straightforward.
When one simply add the CheckedTextView into the xml layout file, it may not appear correctly or not appear at all, because some layout definitions are required. One that works is displayed below:
<CheckedTextView android:id="@+id/ckb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_gravity="right"
android:checked="false"
android:clickable="false"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
/>
Now a full example using CheckedTextView is displayed below. The first box shows the layout of xml of the main Activity. The second one depicts the layout xml of the list row. Finally, the third one displays the code of the activity.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vw1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dip"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="4" >
<TextView android:id="@+id/text1"
android:textSize="12sp"
android:textStyle="bold"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingTop="6dip"/>
<TextView android:id="@+id/text2"
android:textSize="12sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"/>
</LinearLayout>
<LinearLayout
android:id="@+id/cbxLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
>
<CheckedTextView android:id="@+id/ckb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_gravity="right"
android:checked="false"
android:clickable="false"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vw1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dip"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="4" >
<TextView android:id="@+id/text1"
android:textSize="12sp"
android:textStyle="bold"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingTop="6dip"/>
<TextView android:id="@+id/text2"
android:textSize="12sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"/>
</LinearLayout>
<LinearLayout
android:id="@+id/cbxLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
>
<CheckedTextView android:id="@+id/ckb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_gravity="right"
android:checked="false"
android:clickable="false"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
/>
</LinearLayout>
</LinearLayout>
<?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">
<ListView
android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/button_label"
android:gravity="center_horizontal"
/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/button_label"
android:gravity="center_horizontal"
/>
</LinearLayout>
package com.checkboxlist;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class CheckBoxList extends ListActivity implements OnClickListener {
private ArrayList<Integer> selectedItems = new ArrayList<Integer>();
private final String SELECTED_ITEM_KEY = "selected_items";
public final String TEXT_KEY_1 = "title";
public final String TEXT_KEY_2 = "description";
public final String ITEM_ID = "id";
public final String IMG_KEY = "img";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listviewmult);
findViewById(R.id.button).setOnClickListener(this);
// list data
List<Map<String, Object>> resourceNames = new ArrayList<Map<String, Object>>();
generateData(resourceNames);
MyAdapter notes = new MyAdapter(this, resourceNames,
R.layout.listrowmult, new String[] { TEXT_KEY_1, TEXT_KEY_2,
IMG_KEY, ITEM_ID }, new int[] { R.id.text1, R.id.text2,
R.id.img }, selectedItems);
setListAdapter(notes);
}
private void generateData(List<Map<String, Object>> resourceNames) {
// TODO here you will fill resourceNames with your own data
Map<String, Object> data;
int NUM_ITEMS = 50;
for (int i = 0; i <= NUM_ITEMS; i++) {
data = new HashMap<String, Object>();
data.put(ITEM_ID, i);
data.put(TEXT_KEY_1,
getString(R.string.list_item) + " " + Integer.toString(i));
data.put(TEXT_KEY_2, getString(R.string.description));
data.put(IMG_KEY, R.drawable.listicon);
resourceNames.add(data);
}
}
/*
* Restores list selection
*/
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
selectedItems.addAll(state.getIntegerArrayList(SELECTED_ITEM_KEY));
}
/*
* When the device is rotated, this activity is killed This method is called
* when activity is about to be killed and saves the current list selection
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntegerArrayList(SELECTED_ITEM_KEY, selectedItems);
}
/*
* Prints on screen the currently selected items
*/
public void onClick(View target) {
// TODO execute your action here
StringBuilder strText = new StringBuilder();
strText.append(getString(R.string.selected));
Collections.sort(selectedItems);
boolean first = true;
for (Integer cur : selectedItems) {
if (first) {
strText.append(cur);
first = false;
} else {
strText.append(", " + cur);
}
}
Toast.makeText(getApplicationContext(), strText.toString(),
Toast.LENGTH_LONG).show();
}
class MyAdapter extends SimpleAdapter {
List<? extends Map<String, ?>> resourceNames;
OnItemClickListener listener = null;
ArrayList<Integer> selectedItems;
String[] strKeys;
int[] ids;
public MyAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to,
ArrayList<Integer> selectedItems) {
super(context, data, resource, from, to);
this.selectedItems = selectedItems;
resourceNames = data;
strKeys = from;
ids = to;
}
/*
* Returns a view to be added on the list When we scroll the list, some
* items leave the screen becoming invisible to the user. Since creating
* views is an expensive task, we'd rather recycle these not visible
* views, that are referenced by convertView, updating its fields
* values.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// used to improve performance, since we call findViewById
// only once for each created, but not recycled, view
ViewHolder holder;
if (listener == null)
listener = new OnItemClickListener(selectedItems);
// view to be recycled
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listrowmult, null);
holder.tv1 = (TextView) convertView.findViewById(R.id.text1);
holder.tv2 = (TextView) convertView.findViewById(R.id.text2);
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.ckb = (CheckedTextView) convertView.findViewById(R.id.ckb);
holder.cbxLayout = (LinearLayout) convertView.findViewById(R.id.cbxLayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Map<String, ?> currentData = resourceNames.get(position);
// updates list items values
holder.tv1.setText(currentData.get(strKeys[0]).toString());
holder.tv2.setText(currentData.get(strKeys[1]).toString());
holder.img.setImageResource((Integer) currentData.get(strKeys[2]));
holder.ckb.setChecked(selectedItems.contains((Integer) currentData
.get(strKeys[3])));
holder.cbxLayout.setId((Integer) currentData.get(strKeys[3]));
holder.cbxLayout.setOnClickListener(listener);
return convertView;
}
}
/*
* Holds references to list items
*/
class ViewHolder {
TextView tv1, tv2;
ImageView img;
CheckedTextView ckb;
LinearLayout cbxLayout;
}
/*
* Called when a list item is clicked
*/
class OnItemClickListener implements OnClickListener {
ArrayList<Integer> selectedItems;
public OnItemClickListener(ArrayList<Integer> selectedItems) {
this.selectedItems = selectedItems;
}
public void onClick(View v) {
// handles list item click
CheckedTextView ckb = (CheckedTextView ) v.findViewById(R.id.ckb);
boolean checked = ckb.isChecked();
// updates selected list
if (checked) {
selectedItems.remove(new Integer(v.getId()));
} else {
selectedItems.add(v.getId());
}
// update check box value
ckb.setChecked(!checked);
}
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class CheckBoxList extends ListActivity implements OnClickListener {
private ArrayList<Integer> selectedItems = new ArrayList<Integer>();
private final String SELECTED_ITEM_KEY = "selected_items";
public final String TEXT_KEY_1 = "title";
public final String TEXT_KEY_2 = "description";
public final String ITEM_ID = "id";
public final String IMG_KEY = "img";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listviewmult);
findViewById(R.id.button).setOnClickListener(this);
// list data
List<Map<String, Object>> resourceNames = new ArrayList<Map<String, Object>>();
generateData(resourceNames);
MyAdapter notes = new MyAdapter(this, resourceNames,
R.layout.listrowmult, new String[] { TEXT_KEY_1, TEXT_KEY_2,
IMG_KEY, ITEM_ID }, new int[] { R.id.text1, R.id.text2,
R.id.img }, selectedItems);
setListAdapter(notes);
}
private void generateData(List<Map<String, Object>> resourceNames) {
// TODO here you will fill resourceNames with your own data
Map<String, Object> data;
int NUM_ITEMS = 50;
for (int i = 0; i <= NUM_ITEMS; i++) {
data = new HashMap<String, Object>();
data.put(ITEM_ID, i);
data.put(TEXT_KEY_1,
getString(R.string.list_item) + " " + Integer.toString(i));
data.put(TEXT_KEY_2, getString(R.string.description));
data.put(IMG_KEY, R.drawable.listicon);
resourceNames.add(data);
}
}
/*
* Restores list selection
*/
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
selectedItems.addAll(state.getIntegerArrayList(SELECTED_ITEM_KEY));
}
/*
* When the device is rotated, this activity is killed This method is called
* when activity is about to be killed and saves the current list selection
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntegerArrayList(SELECTED_ITEM_KEY, selectedItems);
}
/*
* Prints on screen the currently selected items
*/
public void onClick(View target) {
// TODO execute your action here
StringBuilder strText = new StringBuilder();
strText.append(getString(R.string.selected));
Collections.sort(selectedItems);
boolean first = true;
for (Integer cur : selectedItems) {
if (first) {
strText.append(cur);
first = false;
} else {
strText.append(", " + cur);
}
}
Toast.makeText(getApplicationContext(), strText.toString(),
Toast.LENGTH_LONG).show();
}
class MyAdapter extends SimpleAdapter {
List<? extends Map<String, ?>> resourceNames;
OnItemClickListener listener = null;
ArrayList<Integer> selectedItems;
String[] strKeys;
int[] ids;
public MyAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to,
ArrayList<Integer> selectedItems) {
super(context, data, resource, from, to);
this.selectedItems = selectedItems;
resourceNames = data;
strKeys = from;
ids = to;
}
/*
* Returns a view to be added on the list When we scroll the list, some
* items leave the screen becoming invisible to the user. Since creating
* views is an expensive task, we'd rather recycle these not visible
* views, that are referenced by convertView, updating its fields
* values.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// used to improve performance, since we call findViewById
// only once for each created, but not recycled, view
ViewHolder holder;
if (listener == null)
listener = new OnItemClickListener(selectedItems);
// view to be recycled
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listrowmult, null);
holder.tv1 = (TextView) convertView.findViewById(R.id.text1);
holder.tv2 = (TextView) convertView.findViewById(R.id.text2);
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.ckb = (CheckedTextView) convertView.findViewById(R.id.ckb);
holder.cbxLayout = (LinearLayout) convertView.findViewById(R.id.cbxLayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Map<String, ?> currentData = resourceNames.get(position);
// updates list items values
holder.tv1.setText(currentData.get(strKeys[0]).toString());
holder.tv2.setText(currentData.get(strKeys[1]).toString());
holder.img.setImageResource((Integer) currentData.get(strKeys[2]));
holder.ckb.setChecked(selectedItems.contains((Integer) currentData
.get(strKeys[3])));
holder.cbxLayout.setId((Integer) currentData.get(strKeys[3]));
holder.cbxLayout.setOnClickListener(listener);
return convertView;
}
}
/*
* Holds references to list items
*/
class ViewHolder {
TextView tv1, tv2;
ImageView img;
CheckedTextView ckb;
LinearLayout cbxLayout;
}
/*
* Called when a list item is clicked
*/
class OnItemClickListener implements OnClickListener {
ArrayList<Integer> selectedItems;
public OnItemClickListener(ArrayList<Integer> selectedItems) {
this.selectedItems = selectedItems;
}
public void onClick(View v) {
// handles list item click
CheckedTextView ckb = (CheckedTextView ) v.findViewById(R.id.ckb);
boolean checked = ckb.isChecked();
// updates selected list
if (checked) {
selectedItems.remove(new Integer(v.getId()));
} else {
selectedItems.add(v.getId());
}
// update check box value
ckb.setChecked(!checked);
}
}
}
Now, the most importat details of the code will be explained.
private void generateData(List<Map<String, Object>> resourceNames) {
// TODO here you will fill resourceNames with your own data
Map<String, Object> data;
int NUM_ITEMS = 50;
for (int i = 0; i <= NUM_ITEMS; i++) {
data = new HashMap<String, Object>();
data.put(ITEM_ID, i);
data.put(TEXT_KEY_1,
getString(R.string.list_item) + " " + Integer.toString(i));
data.put(TEXT_KEY_2, getString(R.string.description));
data.put(IMG_KEY, R.drawable.listicon);
resourceNames.add(data);
}
}
The code above shows where the data is generated. A dumny collection of Map is created. This method should be replaced by the actual data retrieving of ones implementation.
/*
* Prints on screen the currently selected items
*/
public void onClick(View target) {
// TODO execute your action here
StringBuilder strText = new StringBuilder();
strText.append(getString(R.string.selected));
Collections.sort(selectedItems);
boolean first = true;
for (Integer cur : selectedItems) {
if (first) {
strText.append(cur);
first = false;
} else {
strText.append(", " + cur);
}
}
Toast.makeText(getApplicationContext(), strText.toString(),
Toast.LENGTH_LONG).show();
}
The code above display the selected items. Note that is uses the list selectedItems which stores the the elements which were checkec by the user.
class MyAdapter extends SimpleAdapter {
List<? extends Map<String, ?>> resourceNames;
OnItemClickListener listener = null;
ArrayList<Integer> selectedItems;
String[] strKeys;
int[] ids;
public MyAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to,
ArrayList<Integer> selectedItems) {
super(context, data, resource, from, to);
this.selectedItems = selectedItems;
resourceNames = data;
strKeys = from;
ids = to;
}
/*
* Returns a view to be added on the list When we scroll the list, some
* items leave the screen becoming invisible to the user. Since creating
* views is an expensive task, we'd rather recycle these not visible
* views, that are referenced by convertView, updating its fields
* values.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// used to improve performance, since we call findViewById
// only once for each created, but not recycled, view
ViewHolder holder;
if (listener == null)
listener = new OnItemClickListener(selectedItems);
// view to be recycled
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listrowmult, null);
holder.tv1 = (TextView) convertView.findViewById(R.id.text1);
holder.tv2 = (TextView) convertView.findViewById(R.id.text2);
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.ckb = (CheckedTextView) convertView.findViewById(R.id.ckb);
holder.cbxLayout = (LinearLayout) convertView.findViewById(R.id.cbxLayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Map<String, ?> currentData = resourceNames.get(position);
// updates list items values
holder.tv1.setText(currentData.get(strKeys[0]).toString());
holder.tv2.setText(currentData.get(strKeys[1]).toString());
holder.img.setImageResource((Integer) currentData.get(strKeys[2]));
holder.ckb.setChecked(selectedItems.contains((Integer) currentData
.get(strKeys[3])));
holder.cbxLayout.setId((Integer) currentData.get(strKeys[3]));
holder.cbxLayout.setOnClickListener(listener);
return convertView;
}
}
The code above is the custom implementation of the Base Adapter. Basically, for every row displayed it sets the appropriate layout, by inflating it, and set the content values and listeners appropriated to make the list work. Take a time to understand it. Note that the this Adapter was bound in the onCreate(Bundle) event. Also, oberve how it saves data in a Value Object structure, called ViewHolder. It does that to improve performance.
Concluding, studying the posted code one can accomplish to develop a List Activity enabling selection, using Check Boxes by the use of CheckedTextView component.
Can you provide latest version of code. Because of deprecated of Simple Adapter class.
ReplyDelete