困扰我多久的问题终于解决,这里共享下:
原网站:http://www.linuxidc.com/Linux/2011-09/43139p4.htm
我们在每个 ListView 条目的最后增加一个 CheckBox ,即每一个 ListView 条目所包含的内容为:
ImageView 、 TextView 和 CheckBox 。
我们可以通过点击每个条目最后的 CheckBox ,在选中相关 ListView 中的条目。需要注意的地方:
+ 除 CheckBox 外,不让 ListView 中的条目接受 Click 事件
+ 在 ListView 中条目比较多 ( 比较多的意思是,比如,屏幕只能显示 6 个条目,而 ListView 中一共有 8 个条目,也就是说, ListView 的条目数量大于屏幕可以显示的条目数量 ) 的时候,有两个问题需要特别注意:
- 因为 ListView 可以在垂直方向滚动,那么总有一些条目是在屏幕上看不到的,这些看不到的条目,如果你试图用 ListView.GetChildAt(intposition) 去获取它时,你会发现得到的结果将会是 null 。
- 要考虑到 CheckBox 的状态保持。比如程序开始运行后,在屏幕上显示 ListView 中的第 0~ 第5 个条目,第 6 、 7 两个条目在屏幕上不可见,这时候,我们点击第 0 个条目的 CheckBox ,那么这个 CheckBox 就会被显示为 Checked 的状态,然后我们将整个 ListView 向下滚动到底,那么第 0 个条目就不可见了。如果我们再将整个 ListView 向上滚动到头,那么此时第 0 个条目又可见了,如果不做一些处理,我们将会发现,第 0 个条目对应的 CheckBox 本应该处于 Checked 状态,但在它重新出现时,居然自动变成了 unChecked 的状态。
+ 在这个例子中,我们采取第三个例子中自己定义一个 Adapter 的方式来进行相关的处理。所不同的是,在第三个例子中的 CustomizedAdapter.getView ,完全用代码实现返回的 View 及其 Layout ,以及其中所包含的 ImageView 和 TextView 对象,而在这个例子中,我们准备先用 xml 文件,定义一个 xml 文件,用作 ListView 中每个条目的 View ,然后再通过 LayoutInflater 中 inflate 方法,获取 getView 方法所需要返回的 View 对象。
下面开始描述具体的步骤:
1. 在第三例子项目的基础上,我们在 res/layout 文件夹中,创建一个 xml 文件: rowcheckboxlayout.xml ,如下:
这个 rowcheckboxlayout.xml 就是 ListView 中每个条目所需要用的 View 。编辑该 xml 文件,使其内容如下:
<? xml version = "1.0" encoding = "utf-8" ?>
<!-- 这次使用 RelativeLayout-->
< RelativeLayout
xmlns:Android = "http://schemas.android.com/apk/res/android"
Android:id = "@+id/row_checkbox_item"
Android:layout_width = "fill_parent"
Android:layout_height = "wrap_content"
Android:orientation = "horizontal" >
<!-- 用于显示图片 -->
< ImageView
Android:id = "@+id/row_checkbox_icon"
Android:layout_width = "48px"
Android:layout_height = "80px"
/>
<!-- 用于显示文字,注意其相关的属性 -->
< TextView
Android:id = "@+id/row_checkbox_text"
Android:layout_width = "wrap_content"
Android:layout_height = "wrap_content"
Android:textSize = "20px"
Android:layout_toRightOf = "@id/row_checkbox_icon"
Android:layout_marginLeft = "8px"
Android:layout_centerVertical = "true" >
</ TextView >
<!-- 用于显示 ChechBox , 注意其相关的属性 -->
< CheckBox
Android:id = "@+id/row_checkbox"
Android:layout_width = "wrap_content"
Android:layout_height = "wrap_content"
Android:layout_marginLeft = "4px"
Android:layout_marginRight = "10px"
Android:layout_alignParentRight = "true"
Android:layout_centerVertical = "true"
>
</ CheckBox >
<!--
Android:focusable="false"
Android:focusableInTouchMode="false"
如果想让 ListView 中的整个条目可以接收 click 事件,那么需要将
上面两个属性,加入到 CheckBox 对象的属性中即可。
-->
</ RelativeLayout >
2. 定义一个用于记录 ListView 条目状态的 POJO 类: ListItemData.java ,使其内容如下:
package com.pat.gui;
public class ListItemData
{
private int os_id ;
private int drawable_id ;
private boolean selected ;
public ListItemData( int os_id, int drawable_id)
{
this . os_id =os_id;
this . drawable_id =drawable_id;
selected = false ;
}
public int getOs_id()
{
return os_id ;
}
public void setOs_id( int osId)
{
os_id =osId;
}
public int getDrawable_id()
{
return drawable_id ;
}
public void setDrawable_id( int drawableId)
{
drawable_id =drawableId;
}
public boolean isSelected()
{
return selected ;
}
public void setSelected( boolean selected)
{
this . selected =selected;
}
}
3. 创建一个自定义的 Adapter 类,这次我们将自定义的 Adapter 独立出来存放于另外一个 Java 文件 CustomizedAdapter.java 中,而不是像再第三个例子那样,将 CustomizedAdapter 作为内部类。编辑CustomizedAdapter.java ,使之如下:
package com.pat.gui;
import java.util.List;
import Android.content.Context;
import Android.graphics.Color;
import Android.view.LayoutInflater;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.ArrayAdapter;
import Android.widget.CheckBox;
import Android.widget.CompoundButton;
import Android.widget.ImageView;
import Android.widget.TextView;
import Android.widget.Toast;
import Android.widget.CompoundButton.OnCheckedChangeListener;
// 自定义的 Adapter , 重写 getCount 、 getItem 、 getItemId 和 getView 方法。其中的 getView 方法最为重要
class CustomizedAdapter extends ArrayAdapter<ListItemData>
{
// 声明一个 LayoutFlater 对象
private LayoutInflater inflater ;
private Context ctx ;
// 声明一个 List 对象 , 其元素的数据类型为 ListItemData 。因此这个 list 对象实际上
// 就是 ListView 对象的数据。
private final List<ListItemData> list ;
public CustomizedAdapter(Contextctx, List<ListItemData> list)
{
super (ctx,R.layout. rowcheckboxlayout ,list);
this . ctx =ctx;
this . list =list;
inflater =(LayoutInflater)ctx.getSystemService(Context. LAYOUT_INFLATER_SERVICE );
}
public int getCount()
{
return list .size();
}
public ListItemDatagetItem( int position)
{
return list .get(position);
}
public long getItemId( int position)
{
return position;
}
// 返回一个 RelativeLayout 对象 , 其中包括一个 ImageView 、一个 TextView 以及一个 CheckBox
public ViewgetView( int position, View convertView, ViewGroup parent)
{
//getView 方法中的第二个参数 convertView 有时候可能会是 null , 在这样的情况下 ,
// 我们就必须创建一个新的 rowView(ListView 中每一个条目需要用到的 ) 。但是,如果
//convertView 不为 null 的时候,它是什么呢?它实际上就是前面通过 inflate 方法
// 得到的 rowView( 见下面代码 ) 。这种情况主要发生在 ListView 滚动的时候:当一个
// 新的条目 ( 行 ) 出现的时候, Android 首先会试图重复使用被移除屏幕的那些条目所
// 对应的 rowView 对象。由于每一行都有相同的结构,因此可以通过 findViewById 方法
// 得到 rowView 中各个对象,根据相关的数据改变这些对象,然后将 contentView 对象
// 返回,而不需要重新构建一个 rowView 对象。
// 所以,在这里,我们先检查 convertView 是否为 null ,如果是 null 的,那么我们创建
// 一个新的 rowView ,否则,我们重用 convertView 。这样做可以大大减少耗时和耗资源
// 的 inflate 的调用。根据 2010 年 GoogleI/O 大会,这样做比每次都 inflate 的做法的
// 性能快出 150% ,如果 rowView 包含的对象很复杂的话,快出 150% 也许都是低估了。
// 另外, 这样做,还可以节省内存。如果如下面重复利用业已存在的 rowView ,那么
// 仅需要 6 个 rowView 对象即可 ( 假定屏幕可以显示的行数是 6) ,假定每个 rowView 所占用的
// 内存是 6kB( 有图像的时候,超过这个数字很容易 ) ,那么一共需要的内存是 36kB 。如果不
// 采取这种重复利用的方式,在假定有 1000 行,那么所需要的内存就是 6MB 了,而且所需要
// 的内存和 ListView 中的行数有关,这本身也不符合可扩展性的原则,容易造成性能上
// 的不稳定。
final int pos= position;
ViewrowView = (View)convertView;
if (rowView== null )
{
rowView= (View) inflater .inflate(R.layout. rowcheckboxlayout , null , true );
}
// 获得 ImageView 对象
ImageViewiv = (ImageView)rowView.findViewById(R.id. row_checkbox_icon );
// 指定对应 position 的 Image
iv.setImageResource( list .get(pos).getDrawable_id());
// 获得 TextView 对象
TextViewtv = (TextView)rowView.findViewById(R.id. row_checkbox_text );
// 指定对应 position 的 Text
tv.setText( list .get(pos).getOs_id());
// 设定文字颜色
if (position%2== 0)
{
tv.setTextColor(Color. YELLOW );
}
else
{
tv.setTextColor(Color. GREEN );
}
// 为 TextView 对象增加一个 Tag , 以便在后续的处理中 , 可以通过
//findViewWithTag 方法来获取这个 TextView 对象,注意 setTag 的参数可以是任意对象
tv.setTag( "tagTextView" );
// 获得 CheckBox 对象
CheckBoxchkbox = (CheckBox)rowView.findViewById(R.id. row_checkbox );
// 为 CheckBox 对象增加一个 Tag , 以便在后续的处理中 , 可以通过
//findViewWithTag 方法来获取这个 TextView 对象,注意 setTag 的参数可以是任意对象
chkbox.setTag( "tagCheckBox" );
// 为 CheckBox 设定 CheckedChangedListener
chkbox.setOnCheckedChangeListener( new OnCheckedChangeListener()
{
public void onCheckedChanged(CompoundButtonbuttonView, boolean isChecked)
{
// 如果有 CheckBox 被点击了 ( 有可能是由 unchecked 变为 checked , 也有可能是由 checked 变为 unchecked) ,
// 那么,我们在 list 中保存对应位置上的 CheckBox 的状态
list .get(pos).setSelected(isChecked);
StringcheckedItems = "The following items are checked:/n/n" ;
int j= 0; // 一个标记
// 根据 list 中记录的状态 , 输出 ListView 中对应 CheckBox 状态为 checked 的条目
for ( int i= 0; i < list .size(); ++i)
{
if ( list .get(i).isSelected())
{
// 通过 getString 方法 (Context 中定义的 ) 获取 id 对应的字符串
checkedItems+= i + "/t" + ctx .getString( list .get(i).getOs_id())+ "/n" ;
++j;
}
}
if (j== 0)
{
checkedItems+= "NO ITEM CHECKED." ;
}
Toast.makeText ( ctx ,checkedItems, Toast. LENGTH_SHORT ).show();
}
});
// 下面这行特别重要 , 否则 ListView 中的 CheckBox 不能正常显示。
chkbox.setChecked( list .get(pos).isSelected());
return rowView;
}
}
4. 修改 Activity 所对应代码,使之如下:
( 下面代码中被注释的部分,曾想用 OnScrollListener 来处理 ListView 滚动时界面元素的重画,但由于 GetChildAt 可能返回 null ,而导致程序崩溃,详见下面 onScrollStateChanged 方法中的说明 )
package com.pat.gui;
import java.util.ArrayList;
import java.util.List;
import Android.app.Activity;
import Android.os.Bundle;
//import Android.util.Log;
//import Android.view.View;
//import Android.widget.AdapterView;
//import Android.widget.CheckBox;
import Android.widget.ListView;
//import Android.widget.AbsListView.OnScrollListener;
//import Android.widget.AdapterView.OnItemClickListener;
public class ControlListView extends Activity
//implements
//OnItemClickListener
{
// 声明一个 ListView 对象
private ListView listview ;
private CustomizedAdapter adapter ;
// private intFIRST; // 用于记录在 ListView 停止滚动时,第一条在屏幕上可见的 item 的在 ListView 的位置
// private intVISIBLE; // 用于记录在屏幕上显示 item 的条数
// private intTOTAL; // 在 ListView 中 item 的数量
// 定义一个图片资源 ID 数组,代表各种手机操作系统的 logo
private int [] drawableIDs =
{
R.drawable. Android ,
R.drawable. ios ,
R.drawable. wp ,
R.drawable. symbian ,
R.drawable. blackberry ,
R.drawable. palm ,
R.drawable. ophone ,
R.drawable. other
};
// 定义一个字符串 ID 数组 , 用以代表各种不同的手机操作系统名称 , 和 drawableIDs 有一一对应的关系
private int [] os =
{
R.string. Android ,
R.string. ios ,
R.string. wp ,
R.string. symbian ,
R.string. blackberry ,
R.string. palmos ,
R.string. ophone ,
R.string. other
};
@Override
public void onCreate(BundlesavedInstanceState)
{
super .onCreate(savedInstanceState);
setContentView(R.layout. main );
// 获得 ListView 对象
listview =(ListView) this .findViewById(R.id. listview );
listview .setChoiceMode(ListView. CHOICE_MODE_MULTIPLE );
// 构造一个和 listview 对应的 list 对象。 list 用于保存 listview 中各 item 的状态。
final List<ListItemData>list = new ArrayList<ListItemData>();
for ( int i= 0; i < 8; ++i)
{
list.add( new ListItemData( os [i], drawableIDs [i]));
}
// 使用自定义的 Adapter
adapter = new CustomizedAdapter( this ,list);
// 将 adapter 和 listview 关联起来
listview .setAdapter( adapter );
//listview.setOnItemClickListener(this);
//listview.setOnScrollListener(newOnScrollListener()
//{
// publicvoid onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, inttotalItemCount)
// {
// FIRST= firstVisibleItem;
// VISIBLE= visibleItemCount;
// TOTAL= totalItemCount;
// Log.e("111","firstVisibleItem = " + firstVisibleItem + ", visibleItemCount =" +
// visibleItemCount+ ", totalItemCount = " + totalItemCount);
// }
//
// publicvoid onScrollStateChanged(AbsListView view, int scrollState)
// {
// Viewlist_item;
// CheckBoxchk_box;
// //scrollState 等于 0 的时候 , 也就是不滚动的时候 , 分别取出 FIRST , VISIBLE 和TOTAL 的值
// if(scrollState== 0)
// {
// Log.e("111","FIRST = " + FIRST + ", VISIBLE = " + VISIBLE + ",TOTAL = " + TOTAL);
//
// // 仅处理屏幕上可见的 item , 但是 , 即便如此 view.getChildAt 还是有可能返回 null , 从而导致程序崩溃。
// // 疑是 getChildAt 的 bug 。而按道理而言,只要 item 在屏幕上可见,那么 view.getChildAt 不应该返回 null
// // 因此试图通过这种方式来重画 CheckBox 的状态,似乎不可行。
// for(inti = FIRST; i < (FIRST + VISIBLE); ++i)
// {
// list_item= (View)view.getChildAt(i);
// //if(list_item== null)continue;
//
// //if(list_item== null) break;
// //if((list.get(i).isSelected())&& (list_item != null) && (list_item.isShown() == true))
// if(list.get(i).isSelected())
// {
// chk_box= (CheckBox) list_item.findViewWithTag("tagCheckBox");
// chk_box.setChecked(true);
// }
// }
// }
// }
//});
}
//public voidonItemClick(AdapterView<?> parent, View view, int position, long id)
//{
// Log.e("1",""+position);
// Viewlist_item = (View)parent.getChildAt(position);
// CheckBoxchk_box = (CheckBox)list_item.findViewWithTag("tagCheckBox");
// chk_box.setChecked(true);
// adapter.notifyDataSetChanged();
//}
}
运行结果:
点击 Android 、 iOS 和 WindowsPhone 右边的 CheckBox 得到:
向下滚到到底,并点击 Other 右边的 CheckBox ,得到:
现在想上滚到到头,观察 Android 、 iOS 和 WindowsPhone 右边的 CheckBox 的 Checked 是否仍然被保持着:
可以看到状态保持得很好。
本文详细介绍了如何在Android中使用自定义Adapter配合ListView,实现在滚动时保持CheckBox状态的功能,包括创建自定义布局、定义POJO类、实现自定义Adapter及处理滚动事件。
4721

被折叠的 条评论
为什么被折叠?



