作者介绍:
李建,乐成数字通信学院 高级讲师。曾就职于国内数家SP,CP公司,具有丰富的软件、游戏开发经验。并从事多年教学工作,具有丰富的教学经验。目前主要从事OPhone、J2ME开发和教学方面的工作。
概述:
在我们设计的软件中,很多时候需要以列表的形式显示一些信息,要实现这样的功能,OPhone API给我们提供了非常方便的ListView控件和ListActivity用来显示列表信息,但有的时候这些控件并不能满足于我们程序开发的需要,因此,就需要我们定义自己的列表界面。实际上在OPhone中,设计自己的列表界面和内容十分简单和方便,下面将一步步地进行介绍如何来创建和使用自定义列表项,在这个例子中,我们将实现如下图所示的效果:

要实现上图效果我们需要建立三个类:一个Activity,一个数据类ListContent和一个列表类MyAdapter。
下面进行主界面的设计工作。目前这个版本的OPhone在界面可视化设计方面似乎还做的不够完善,暂时还不能实现类似VB、Delphi、C#那样的拖拽设计界面的功能,因此在界面的设计上,需要程序员通过定义xml文件的方式来布局和摆放空间的大小和位置,当然,我们可以通过使用不同类型的布局管理器来对各个控件进行布局以,以达到很好的自适应效果。
首先我们来设计这个示例程序的主界面:在res/layout里新建一个news_list_form_title.xml文件,在该界面中我们使用LinearLayout布局,并在该布局管理器中添加如下几个控件:
- ImageView:一个图片控件,其功能就是用来显示一幅图片,此处我们用他来显示如下所示的图片

- ImageView:作用同上,此处用来显示如下图图片:

分别用两张图片的原因是我们可以在下面的图片上设置不同的文字内容。
注意:原始图片需要放置到res/drawable目录中,且文件的命名为【0-9】或者【a-z】的组合。
ListView:列表控件,用来显示所有的列表项内容,我们在图1中看到的列表项就是通过该空间表现出来的。
这三个控件的顺序及其与布局管理的关系如下图所示:

news_list_form_title.xml文件的代码可以参考如下范例:
<?xml
version="1.0"
encoding="utf-8"?>
<LinearLayout
android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android
rientation="vertical">
<ImageView
android:id="@+id/ImageView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/form_title">
</ImageView>
<ImageView
android:id="@+id/ImageView02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/form_news">
</ImageView>
<ListView
android:id="@+id/ListView01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:dividerHeight="2dip">
</ListView>
- </LinearLayout>
注意:上面代码中加粗及高亮部分代表列表项之间分割线的粗细程度,此处使用“dip”作为其衡量单位。
设置好控件的位置和内容后,其效果将如下图所示:

列表项的设计:
接下来我们要定义ListView界面的内容,在这里我们设计列表中的每项需要容纳三块内容:标题(title),描述(description)和图标(icon)。对于界面的设计此处仍然是在res/layout里新建一个文件,名字定义为news_list.xml,此界面最终的效果如下图所示:

要实现这样的效果,我们需要使用如下几个控件:
ImageView:用来显示上图中最左面的图标。
TextView:一个文本控件,其实质就是一个Label,用来显示不能被编辑的文本内容,此处用来显示标题内容。
TextView:作用同上,此处用来显示描述信息。
注意:我们可以更改TextView显示字体的颜色文本和样式。
这三个控件和对应的布局管理器的关系如下图所示:

实现该界面的xml文件可以参考如下代码:
<?xml
version="1.0"
encoding="utf-8"?>
<LinearLayout
android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android
rientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/news_icon"
android:id="@+id/news_icon"
android:layout_marginLeft="5dip"
android:layout_marginTop="5dip"
- android:layout_marginBottom="5dip">
</ImageView>
<LinearLayout
android:id="@+id/LinearLayout03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android
rientation="vertical"
android:layout_weight="1">
<TextView
android:text="title"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textStyle="bold"
android:textSize="25dip"
android:layout_marginLeft="5dip"
- android:textColor="@color/white">
</TextView>
<TextView
android:text="description"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
android:id="@+id/description"
android:textSize="20dip"
android:layout_marginLeft="5dip">
</TextView>
</LinearLayout>
- </LinearLayout>
数据类的定义:
定义好界面之后我们就可以开始写代码了。由于列表中的每项需要容纳三块内容:标题(title),描述和图标,定义好内容后,就需要建立一个数据类用来描述列表中每一项的具体内容,我们把该数据类命名为ListContent。在这个类中定义三个属性,title,description,icon,同时分别定义设置和获取属性值的set**( )和get**( )方法。
列表类MyAdapter的实现:
定义数据类之后就需要定义列表项类MyAdapter,这个类需要继承BaseAdapter。对于BaseAdapter这个类我们可以查阅API文档,其实它的作用就是用来显示列表中每一项所包含的具体内容。
注意:BaseAdapter是一个抽象类,因此继承需要实现4个抽象方法。
方法列表如下图所示:

该类代码如下所示:
public class MyAdapter extends BaseAdapter- {private LayoutInflater mInflater;
- // 保存所有文章信息的列表private List<ListContent> newsList;
//MyAdapter的构造器- public MyAdapter(Context context, List<ListContent> list){
- // 参数初始化
mInflater = LayoutInflater.from(context);
this.newsList = list;}- //因继承BaseAdapter,需覆盖以下方法 public int getCount()
- { return newsList.size();
- }public Object getItem(int position)
- { return newsList.get(position);
- }public long getItemId(int position)
- { return position;
- }public View getView(int position, View convertView, ViewGroup parent)
- { return convertView;
- }
- }
注意:上面的代码中有两个变量需要解释一下:
1. LayoutInflater:用来把xml描述的界面转换为View对象,以便我们能提取出该界面中的各个控件对象。
2. List<ListContent>:数据列表,也就是列表的数据源,在这里我们使用java.util包下面的List作为数据容器,其内部的数据类型为前面定义的ListContent类的对象。
ViewHolder类的实现
由于我们自定义的列表由标题,描述,图标这三部分组成,因此,在这里我们还需定义一个类ViewHolder用来管理每个列表项所包含的控件,代码如下:
private class ViewHolder- { TextView title;// 标题
- TextView decription;// 摘要 ImageView icon;// 缩略图
- }
实际上,可以将此类定义成MyAdapter的内部类。
建立数据关联:
定义好这些内容之后我们就可以在getView( )方法中增加具体的数据内容了。此处,我们使用一组随意的数据进行测试。这里只需要把MyAdapter类中定义的newsList容器中的数据逐个拿出并和ViewHolder中定义的各个控件相关联即可。由于需要逐个为TextView和ImageView控件赋值,此处就用到了前面声明的LayoutInflater mInflater。其使用方法如下代码所示:
- convertView = mInflater.inflate(R.layout.news_list, null);
这样就可以通过convertView来找到具体的控件对象并给ViewHolder中的各个属性进行赋值了,代码如下:
//初始化holder的text与icon
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
holder.decription = (TextView)convertView.findViewById(R.id.description);
holder.icon = (ImageView) convertView.findViewById(R.id.news_icon);- convertView.setTag(holder);
接下来的工作就是把列表中的数据内容赋值给ViewHolder中的各个属性,以达到最终的显示要求,代码如下所示:
//设置图标,title和description holder.icon.setImageBitmap(((ListContent)newsList.- get(position)).getIcon());
- holder.title.setText(((ListContent)newsList.get(position)).getTitle());
holder.decription.setText(((ListContent)newsList.- get(position)).getDescription());
注意:上面代码中的“position”是getView()方法中的参数,这个方法会被自动调用,并主动传入position数值,它代表当前列表中被选中的项的索引。
Activity的实现
最后,我们来实现Activity。对Activity的实现想必大家都已经非常熟悉了,只需要继承Activity并设置好要显示的界面即可,此处我们定义该类名为NewsList。
另外,在这个类中,需要提供一些测试数据,此处我们使用ArrayList作为数据容器。为了处理方便,这里定义了一个add()方法用来创建数据容器并加入一些测试数据内容,示例代码如下:
public void addList()- {
rssList=new ArrayList<ListContent>(); - //增加10条测试数据 ListContent listContent;
- for(int i=0;i<10;i++) {
listContent=new ListContent();listContent.setDescription("description: "+ i );- listContent.setTitle("title: "+i);listContent.setIcon(BitmapFactory.decodeResource
- (context.getResources(),R.drawable.news_icon));
- rssList.add(listContent); }
- newsListView.setAdapter(new MyAdapter(this,rssList));
- }
注意:“setAdapter()”方法的作用就是告诉ListView控件按照我们自定义的列表MyAdapter样式和内容来显示。
剩下的工作就是定义NewsList类Activity的构造方法了,代码如下所示:
public void onCreate(Bundle bundle)- {super.onCreate(bundle);
context=this; //无标题栏- requestWindowFeature(Window.FEATURE_NO_TITLE); this.setContentView(R.layout.news_list_form_title);
newsListView=(ListView)this.findViewById(R.id.ListView01);addList();- }
注意:在这个例子中,我们需要隐藏手机本身的状态栏,要实现这个效果,我们需要在NewsList的构造方法中做如下方法调用:
- requestWindowFeature(Window.FEATURE_NO_TITLE);
到目前为止,我们把自定义列表所需要的全部准备工作都已经做好,下面就可以运行看看效果了。
列表项的事件处理:
我们虽然画出了列表的内容,但是你会发现,不管是用按键还是鼠标点击任何一个列表项的时候,都不会有任何反应,这里的原因就是我们还没有处理列表的事件。下面简单的对列表的事件进行一下介绍。
OPhone对控件事件的处理仍然采用Java中传统的“授权事件模型”,即需要包含事件源和监听器两部分内容。对“授权事件模型”的理解只需要弄明白下面三部分内容即可:
事件源既是产生事件的控件。一般是一个具体的控件对象。 监听器是用来对事件源进行监听的,一般为接口形式存在。 - 所谓的“授权”既是对监听器要进行注册。
了解了授权事件模型之后,我们就按上面的步骤逐一实现,来处理列表事件。我们先来处理监听器这部分。上面介绍过,监听器实际上就是由接口来实现的,在本例中,我们需要让Activity这个类来实现这些接口,此处我们需要修改NewsList定义,修改后的类定义如下所示:
public class NewsList extends Activity- implementsOnItemSelectedListener,
- OnItemClickListener,
- OnItemLongClickListener
这里我们实现了三个接口,从这三个接口的名字我们就能大概分析中每个监听器的作用:
OnItemSelectedListener:处理选项被选择事件 OnItemClickListener:处理选项被点击事件 - OnItemLongClickListener:处理选项长时间点击事件
实现了接口之后,就需要定义接口中定义的抽象方法,代码如下所示:
//OnItemSelectedListener接口中方法,当选项变化时调用- public void onItemSelected(AdapterView parent, View v,int position, long id)
- { Log.v("NewsList","==onItemSelected==");
- }//OnItemSelectedListener接口中方法
- public void onNothingSelected(AdapterView parent) {
- Log.v("NewsList","==onNothingSelected==");}
- //OnItemClickListener中方法,当鼠标选中时调用public void onItemClick(AdapterView parent, View v, int position,
- long id) {
- Log.v("NewsList","==onItemClick==");}
- //OnItemLongClickListener中方法,用来响应长按键public boolean onItemLongClick(AdapterView parent, View v,
- int position, long id){
- Log.v("NewsList","==onItemLongClick==");return true;
- }
最后一步别忘记,就是“授权”,也就是注册,注册方法如下:
//设定监听器- newsListView.setOnItemSelectedListener(this); newsListView.setOnItemClickListener(this);
- newsListView.setOnItemLongClickListener(this);
注意:此处的newsListView就是我们定义的ListView对象,本段代码可以放置到NewsList的构造方法中。
运行测试:
添加完上述代码后,再次运行模拟器,可以选择列表的项、点击等操作然后观察logcat中是否有输出的日志信息。

总结:
OPhone提供了大量方便、易用、漂亮的控件供我们使用,同时也提供了灵活的扩展的机制来方便我们修改,当然,本文只是简单的向大家介绍了自定义控件的使用方法,更多的使用方法和技巧还需要广大的开发者去研究并分享。同时,由于对OPhone研究不深,文中错误在所难免,还希望各位朋友及时指正,共同提高。最后附上源代码供大家参考。