Android中自定义ListView

Author : lovecicy

在使用Android的ListView的时候,Android系统自带了一些简单的布局,但是如果要做出比较复杂的显示列表——像新浪微博的微博列表,就需要对列表的显示进行自定义。

在自定义时,大致需要一下步骤:

一、在res/layout/文件下新建一个布局文件

假设新建一个文件名叫做custom_list_view.xml,此文件用于显示单个列表项,相当于显示一条微博,包含的内容有头像、微博内容、图片、转发数、评论数等。这个布局文件和Activity的布局文件并没有什么不同。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
android:orientation="vertical"
android:descendantFocusability="blocksDescendants">

<TextView
 android:id="@+id/large_text"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/test"
 android:textAppearance="?android:attr/textAppearanceLarge" />

<TextView
 android:id="@+id/small_text"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/test2" />

<ImageView 
 android:id="@+id/image"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"/>
</LinearLayout>

二、覆写BaseAdapter类

Adapter的作用就是将数据集合中的每个条目取出,将条目内的每个数据项放入到对应的控件中。最主要的方法是getView,在显示ListView的时候,每取出列表一个列表项数据,就会调用一次getView方法,并返回要显示的view。

class MyAdapter extends BaseAdapter {
 private LayoutInflater inflater;
 private List<Map<String,Object>> data;
 private TextView largeText;
 private TextView smallText;
 private ImageView imageView;
 private List<View> holder;

 public MyAdapter(Context context,List<Map<String,Object>> data){
 this.inflater = LayoutInflater.from(context);
 this.data = data;
 }

 public int getCount() {
 return data.size();
 }

 public Object getItem(int arg0) {
 return data.get(arg0);
 }

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

@Override
 public View getView(int p, View v, ViewGroup parent) {
 if(v == null){
 v = inflater.inflate(R.layout.custom_list_view, null);
 largeText= (TextView)v.findViewById(R.id.large_text);
 smallText= (TextView)v.findViewById(R.id.small_text);
 imageView= (ImageView)v.findViewById(R.id.image);
 holder = new ArrayList<View>();
 holder.add(largeText);
 holder.add(smallText);
 holder.add(imageView);
 v.setTag(holder);
 }else{
 holder = (ArrayList)v.getTag();
 }
 ((TextView)holder.get(0)).setText((String)data.get(p).get("large_text"));
 ((TextView)holder.get(1)).setText((String)data.get(p).get("small_text"));((ImageView)holder.get(2)).setImageDrawable(getResources().getDrawable((int)data.get(p).get("drawable_id")));
 return v;
 }
}

三、在Activity中为ListView设置Adapter,并将数据传入Adapter。

通常数据来自数据库查询结果,这里只是简单的将构造的数据传给Adapter。

List<Map<String,Object>> data = new ArrayList<Map<String,Object>>();
Map<String,Object> d1 = new HashMap<String,Object>();
d1.put("large_text", "Large Text1");
d1.put("small_text", "Small Text1");
d1.put("drawable_id", R.id.testImage1);
d2.put("large_text", "Large Tex2t");
d2.put("small_text", "Small Text2");
d2.put("drawable_id", R.id.testImage2);
d3.put("large_text", "Large Text3");
d3.put("small_text", "Small Text3");
d3.put("drawable_id", R.id.testImage3);
d4.put("large_text", "Large Text4");
d4.put("small_text", "Small Text4");
d4.put("drawable_id", R.id.testImage4);
data.add(d1);
data.add(d2);
data.add(d3);
data.add(d4);
ListView list = (ListView)findViewById(R.id.customList);
list.setAdapter(new MyAdapter(this,data));

这样,就可以通过覆写BaseAdapter创建自定义的Adapter并显示自定义的ListView了。

自定义ListView的item无法响应OnItemClickListener的OnItemClick方法问题的解决方案

查看第一步的布局文件,可以看到最外层的RelativeLayout中有一行android:descendantFocusability=”blocksDescendants”,这样就可以响应item的click事件了。

关于这个方案的解释,来自这里

在Android软件设计与实现中我们通常都会使用到ListView这个控件,系统有一些预置的Adapter可以使用,例如SimpleAdapter和ArrayAdapter,但是总是会有一些情况我们需要通过自定义ListView来实现一些效果,那么在这个时候,我们通常会碰到自定义ListView无法选中整个ListViewItem的情况,也就是无法响应ListView的onItemClickListener中的onItemClick()方法,究竟是为什么呢?我之前也在网上查过不少的资料,但是没有发现什么有价值的文章,有一些是建议在Adapter的getView方法中对自己需要响应单击事件的控件进行设置。但是最终的效果并不是特别理想,而且我认为这是一种取巧的方式,并不推荐。之后自己查看了一下ViewGroup的源码,发现了以下的一段常量声明:

public static final int FOCUS_BEFORE_DESCENDANTS = 0×20000;
public static final int FOCUS_AFTER_DESCENDANTS = 0×40000;
public static final int FOCUS_BLOCK_DESCENDANTS = 0×60000;
public static final int FOCUS_BEFORE_DESCENDANTS = 0×20000;
public static final int FOCUS_AFTER_DESCENDANTS = 0×40000;
public static final int FOCUS_BLOCK_DESCENDANTS = 0×60000;
我们看到了一行代码定义的变量的意思是:

“当前View将屏蔽他所有子控件的Focus状态,即便这些子控件是可以Focus的”,

其实这段话的意思就是这个变量代表着当前的View将不顾其子控件是否可以Focus自身接管了所有的Focus,通常默认能获得focus的控件有Button,Checkable继承来的所有控件,这就意味着如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView的Item能被选中的基础是它能获取Focus,也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置为false,这样的话ListView的Item自动获得了Focus的权限,也就可以被选中了,也就会响应onItemClickListener中的onItemClick()方法,然而将ListView的Item Layout的子控件focusable属性设置为false有点繁琐,我们可以通过对Item Layout的根控件设置其android:descendantFocusability=”blocksDescendant”即可,这样Item Layout就屏蔽了所有子控件获取Focus的权限,不需要针对Item Layout中的每一个控件重新设置focusable属性了,如此就可以顺利的响应onItemClickListener中的onItenClick()方法了。

standard
  1. luckyGirl - 2013 年 5 月 13 日 2:48 下午

    Fighting!!

    回复
    • lovecicy - 2013 年 5 月 15 日 12:43 下午

      呵呵

      回复
  2. 石头 - 2013 年 5 月 31 日 12:35 下午

    写的不错。支持一下。

    回复
    • lovecicy - 2013 年 5 月 31 日 12:43 下午

      三块石头?

      回复

Have your say