继承Checkable接口实现ListView多选笔记

本文介绍如何在Android中使用自定义布局实现ListView的多选功能,包括活动代码、布局设置、自定义适配器及多选组件的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:CUSTOM LISTVIEW WITH ABILITY TO CHECK ITEMS


Android’s ListView is a useful component that can allow the user to check items natively. Two SDK samples (List 10 and List 11) show you how to accomplish this using simple CheckedTextView widgets to represent the rows. However, when you want to represent the rows using a custom layout, you’ll need some time to figure out why you items do not get checked.Well, by the end of this tutorial you should know how to do it.


The activity

There is not much inside the activity code : we override the onCreate method in order to setup our custom adapter and an important ListView property.


// Create the adapter to render our data
adapter = new ItemListAdapter(this, data);
setListAdapter(adapter);    
// Get some views for later use
listView = getListView();
listView.setItemsCanFocus(false);

The method showSelectedItems() will show you how to actually retrieve the selected items. It is worth noting that at least in Android 1.6, the values returned by the ListView#getCheckItemIds() method are incorrect (as notified in that bug report). As a consequence, always prefer the use ofListView#getCheckedItemPositions() as shown below.


final SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
if (checkedItems == null) {    
  // That means our list is not able to handle selection    
  // (choiceMode is CHOICE_MODE_NONE for example)    
  return;
}

// For each element in the status array
final int checkedItemsCount = checkedItems.size();
for (int i = 0; i < checkedItemsCount; ++i) {    
  // This tells us the item position we are looking at    
  final int position = checkedItems.keyAt(i);    

  // And this tells us the item status at the above position    
  final boolean isChecked = checkedItems.valueAt(i);    

  // And we can get our data from the adapter like that    
  final Item currentItem = adapter.getItem(position);
}

The activity layout

Here again, nothing too fancy. We declare our ListView having the ID id/android:list so that our ListActivity can find it easily. Apart from that, you can also set the choice mode here if you want by using the attribute android:choiceMode which can be set to “singleChoice” (only one item can be selected at a time), “multipleChoice” (several items can be selected at the same time) and “none” (no selection is possible).

The adapter

Our adapter is implemented in order to produce the custom row views from an XML file. This is a very common task that is presented in many tutorials (Here is one). Basically, the idea is to override the Adapter#getView(int, View, ViewGroup) method, inflate the View from our XML file describing a row’s layout and set some properties of the widgets in that layout (such as the ID or the caption or the text color …).

The row layout

For the purpose of this article, we have setup a RelativeLayout to show the information in 3 colums: the first one in bold show the ID, the second one shows a caption and the third one contains a small checkbox to show the user whether this row is selected or not. Until now, nothing fancy either.

Now if you look closer at the layout declaration, you will see that we did not exactly use a standard RelativeLayout:

<fr.marvinlabs.widget.CheckableRelativeLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  xmlns:marvinlabs="http://schemas.android.com/apk/res/fr.marvinlabs.selectablelisttutorial"  
  android:layout_width="fill_parent"  
  android:layout_height="fill_parent">

We are using here a custom layout that we will detail in the next paragraph. And this is where the magic happens. Note that all the widgets of the layout have their android:focusable property set to false. If not, the parent view (our custom RelativeLayout) will not be able to receive the ListView events to indicate that the user want to check/uncheck the row.

The CheckableRelativeLayout widget

If you change the row layout and use the standard RelativeLayout, nothing will happen. Indeed, when the user clicks the row, the ListView will look for a widget implementing the Checkable interface. The RelativeLayout does not.

This is where we create a widget that can act just like our initial RelativeLayout but which also implements the Checkable interface:

public class CheckableRelativeLayout extends RelativeLayout implements Checkable {  
  private boolean isChecked;  
  private List checkableViews;

Our custom widget will inherit from RelativeLayout. You can also implement the same kind of custom widget for the other layout classes such as LinearLayout. Our custom class will hold two additional properties:

  • isChecked is used to keep the status of the layout (checked or not)
  • checkableViews is used to hold a reference to each of the checkable views located in our layout. This will allow to pass the checked value to those views and have them updated automatically.

Then comes the implementation of the Checkable interface. Nothing complicated, but you will notice that we also tell our Checkable children when our isChecked status is changed.

// @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) {       
    // Pass the information to all the child Checkable widgets       
    c.setChecked(isChecked);      
  }    
}    

// @see android.widget.Checkable#toggle()    
public void toggle() {      
  this.isChecked = !this.isChecked;      
  for (Checkable c : checkableViews) {         
    // Pass the information to all the child Checkable widgets       
    c.toggle();      
  }    
}

Finally, we would like to discover automatically the child widgets that implement the Checkable interface. The best place to do this is when we have finished to inflate the view.

@Override    
protected void onFinishInflate() {      
  super.onFinishInflate();      
  final int childCount = this.getChildCount();      
  for (int i = 0; i < childCount; ++i) {       
    findCheckableChildren(this.getChildAt(i));     
  }    
}    

/**    
 * 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 &lt; childCount; ++i) {         
      findCheckableChildren(vg.getChildAt(i));       
    }     
  }    
}

Limitations and conclusion

There is a single limitation I know of, and is a real pain: if the user clicks the CheckBox, the event does not get captured by our customised layout. If anybody has an idea, feel free to post it here…


Edit January 20th, 2011: I had a suggestion from a reader to overcome the above limitation. Here is his way to address the problem which I think is quite clever and painless to implement.

Hi,

Thanks for your custom check list view. It helps me to make mine working.To make it working when the user click on the check box just override the CheckBox class and add the following function:

@Override
public boolean onTouchEvent(MotionEvent event) {  
  //super.onTouchEvent(event);  
  return false;
}

Best regards,

Cédric Caron

In short, the idea is to make the Checkbox not respond to touch events and make it say it did not capture any event. This will pass the event to the container (our custom layout) which will then simply select the list item. For completeness, we might also want to override the other event handling functions (onTrackballEvent, onKey*, etc.). The source code provided in this tutorial now includes those changes. Thanks again to Cédric for providing this solution.

I hope this tutorial was useful and clear enough. Do not hesitate to post comments here or on our forum. The full source code to this tutorial is available just below.

Download

As always, the source code of this tutorial is available on our GitHub account.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值