quickisbest 2013-01-22
Adapter软件设计模式在Android中的应用
目录
1.本文摘要3
2.界面体验4
2.1.ListView显示声音文件列表4
2.2.Grid显示图片列表5
2.3.Gallery显示图片6
3.界面元素的分解6
4.Adapter的由来8
5.Adapter接口9
6.ArrayAdapter9
6.1.成员变量9
6.2.函数getCount10
6.3.函数getItem10
6.4.函数getView10
7.Adapter类图11
8.Activity和Adapter的关系12
8.1.ListActivity12
8.2.GridView13
9.ListView,GridView和Gallery开发步骤14
10.开发中的一个小技巧15
11.示例代码15
11.1.继承Activity定义自己的Activity15
11.2.使用XML定义自己的Activity的Layout16
11.3.使用XML定义自己的子Item的视图16
11.4.实现自定义Adapter。一般基类是BaseAdapter17
11.5.在onCreate函数中获取GridView实例,设置好自己定义的Adapter18
12.总结18
1.本文摘要
在Android虚拟项目中,我做的是媒体播放器部分。里面有一个功能就是需要让用户选择一个文件如mp3,然后进行播放。也就是说需要以列表的方式显示媒体列表让用户进行选择。这样就用到了ListActivity。同时对于图片浏览,开始的想法是操作上和PC上一致,也就是以缩略图的方式供用户选择,然后使用ImageView显示选择的图片。这样就用到了GridView。不过Android对图片似乎有更好的诠释,那就是Gallery。翻译成中文就是“长廊”。有点象是画展中的“画廊”的概念,非常的形象。
在对ListActivity,ListView,Grid,Gallery的学习和使用中,我发现Android广泛的利用了Adapter软件设计模式。通过这种设计模式,可以非常方便的开发出非常人性话的界面,同时对于数据,显示以及显示的维护做到了非常好的分离,非常的符合软件设计模式中数据和显示分离,低耦合度的思想。
本文通过实际的例子,一方面是希望开发人员不仅能体会到Android开发人性化界面的方便,另一方面是希望开发人员在开发ListView,Grid,Gallery的时候能知道需要实现哪些类,哪些函数,同时也能知道为什么需要实现类和函数。再者就是再次体会Adapter软件设计模式的优点,能对以后的软件开发设计起到一定的帮助和指导。
2.界面体验
2.1.ListView显示声音文件列表
声音列表的显示方式可以多样的。可以只是显示声音文件名,也就是只有一个TextView,也可以显示成如上左图的显示一个声音图片和文件名,点击每个Item的时候触发事件。也可以如上右图显示图片,文件名,歌唱家,声音的时长,同时显示一个按钮,当用户点击按钮的时候触发相关事件。
2.2.Grid显示图片列表
上面的左图是Android开机的界面,他显示了当前所有注册为LAUNCHER的应用。可以看到在一个网格中的每一个Item不仅显示了一个图片,还包括文字。
上面的右图是仿照左图所做的一个示例程序。同样是显示一个图片和相关的问题。把他列出来是想说我们自己也能实现这种看似复杂的界面。
2.3.Gallery显示图片
这个界面由两部分组成。一个是上面的Gallery,显示的是所有的图片的一个列表。下面是ImageView。当选择Gallery中的一个图片时,ImageView会显示其大的图片,这样来实现图片的浏览。下面的ImageView不是本文的重点,本文的重点是上面的Gallery。下面会做一些详细的探讨。
3.界面元素的分解
下图是对ListActivity的一个界面分解。可以看到最大的一个容器是Activity。其包括一个TextView显示字符串XBtnList,一个ListView显示了文件的详细列表。其中ListView又是一个Container,他包括了一个子View。这个子View我们可以自己定制。这里包括一个ImageView,三个TextView和一个ImageButton。
上面的分析我们可以这样理解。Activity负责其子View的显示。也就是XBtnList字符串的显示和ListView的显示。ListView负责其每个Item在ListView中的显示,但具体到每个Item视图如何显示,ListView并不负责。每个子Item试图的显示是由他自己来负责。也就是ImageView显示什么样的图片,三个TextView显示什么样的文本,字体,颜色,ImageButton显示什么样的图片,以及这些View在子Item视图中的显示位置,内容由子Item试图来实现。
对Grid做同样的分析,具有同样的结果。也就是Activity负责子视图即Grid视图的在Activity中的显示。而Grid中具体显示什么样的元素由子包含的子View来负责。在上面的例子中每个子View实际上包括一个ImageView和一个TextView。
同样的对于Gallery。也是由Gallery来负责其每个子View的显示。子view负责自己的显示。可以只包含一个ImageView,也可以是只有一个TextView,还可以是一个ImageView和一个TextView的组合。
4.Adapter的由来
对于ListView,Grid和Gallery来说,其显示的是一个列表,也就是多个数据。那么这些数据从哪里来呢,数据和ListView是怎么关联起来的呢?
一般来说,数据可以来自一个数组,一个List,或者数据库中的游标。这些是程序开发中用到的非常多的数据集合的表示方式。但是ListView和数据并没有直接的关系,主要的原因是数据可以来自不同的方式,ListView需要的数据可以是数据中的一个子集,另一个原因是ListView还需要显示每个子View。而子View如何显示数据是不负责的。否则就有违数据和显示分离的思想。
这样,我们就必须在数据和ListView中间加入一个第三者来关联数据和ListView,不仅能获取到数据,而且能把获取到的数据以自己定制的方式呈现出来,并把这个呈现转交给ListView来进行显示。
这个第三者就是Adapter,设计模式就是Adapter设计模式。Adapter的作用是把一些不兼容的,不能直接访问的适配成能兼容的,能访问的。比如说第三方提供了一个类,这个类的很多功能都是我们自己需要的,但是不全面,而且函数定义还有重复。那么可以使用类级别上的Adpater,也就是包含开发一个自己的类,其包含第三方类的一个实例,内部实现通过第三方类来代理。也可以继承上实现Adapter。
Android的很多Adapter其实就是类级别上的适配。我们下面会看一下ArrayAdapter类的实现。就会发现他就是一个类级别上的适配。
5.Adapter接口
前面讲过,Adapter能把数据适配成ListView能访问的形式。下面我们看看我们需要实现哪些函数。
publicinterfaceAdapter
{...
publicabstractintgetCount();
publicabstractObjectgetItem(inti);
publicabstractViewgetView(inti,Viewview,ViewGroupvg)
}
这是Adapter的定义的主要接口。也就是说我们自己的Adapter需要实现getCount函数来返回Item的个数,getItem来返回指定Item的数据,getView来返回一个数据初始化的子视图给ListView。
从上面的接口我们似乎可以看到,我们前面的分析是对的。也就是Adapter本身不维护数据,数据保存在数据存储区中如Array。但是Adapter适配了数据,如getCount返回数据的个数,getItem返回指定的数据。同时Adapter还维护数据的显示,也就是Item子视图的显示,表现就是需要返回一个View给ListView。
我们可以通过Android的类ArrayAdapter来看看。
6.ArrayAdapter
6.1.成员变量
下图是ArrayAdapter的主要成员变量的定义图。我们可以清楚的看到其包括一个类型为ArrayList的原始数据mOriginalValues。还包括一个List数据类型的mObjects。也就是ArrayAdapter适配了ArrayList。很明显这是一个类级别上的适配。这里还有一个mObjects的列表主要还实现了数据的过滤。这个不在这里讨论。
第二就是大家还可以看到还包括一个类型为LayoutInflater的成员变量mInflater。这个就是用来从xml文件中建立View的一个类。也就是通过他可以建立View的实例返回给ListView。
6.2.函数getCount
publicintgetCount(){
returnmObjects.size();
}
可以看到他返回了mObjects的节点个数。
6.3.函数getItem
publicObjectgetItem(intposition)
{
returnmObjects.get(position);
}
可以看到他返回了指定节点的数据。
6.4.函数getView
publicViewgetView(intposition,
ViewconvertView,
ViewGroupparent)
{
returncreateViewFromResource(position,convertView,
parent,mResource);
}
privateViewcreateViewFromResource(intposition,
ViewconvertView,ViewGroupparent,
intresource)
{
Viewview;
if(convertView==null)
view=mInflater.inflate(resource,parent,false);
else
view=convertView;
TextViewtext;
try
{
if(mFieldId==0)
text=(TextView)view;
else
text=(TextView)view.findViewById(mFieldId);
}
catch(ClassCastExceptione)
{
Log.e("ArrayAdapter","Youmust...IDforaTextView");
thrownewIllegalStateException("ArrayAdapter...",e);
}
text.setText(getItem(position).toString());
returnview;
}
可以清楚的看到,函数调用了LayoutInflater类从xml资源中创建一个视图,并获取视图中的子控件TextView,然后初始化TextView,也就是设置显示文本。最后返回这个View。
从这里我们也可以看到,Adapter维护了子Item视图的显示。
7.Adapter类图
Adapter类包含很大的一个家族。从下面的类图就可以看到其复杂程度。不过还好,一般来说我们只要使用其子类如ArrayAdapter,SimpleCursorAdapter。对于比较复杂一点的,如上面显示文件列表的ListView,就得实现BaseAdapter了。后面会由详细的例子。
8.Activity和Adapter的关系
其实到现在,基本上我们就可以知道Activity和Adapter之间的关系。Activity是一个容器窗口,ListView,Grid或者是Gallery都是一个一个的控件,不过这些控件也是容器,可以包含其他的子Item视图。而这些子视图是由Adapter来维护的。这就是Adapter和Activity之间的关系。非常松散的一个关系。可以看看下面的类图就知道了。
8.1.ListActivity
一个ListActivity其实就是一个Activity。不过他包含一个ListView。由ListView来维护List列表的显示。其包含ListAdapter,也就是Adapter。由Adapter来维护子Item视图的显示。不过在这里需要注意的是,ListActivity的ListView的Id是固定死的。你也可以自己定义自己的Activity,然后包含自己的ListView。
8.2.GridView
Activity包含GridView,Id不是象ListActivity一样固定死的,可以自己定义。然后GridView会维护自己的Adapter,Adapter维护子Item视图的显示。
9.ListView,GridView和Gallery开发步骤
继承Activity定义自己的Activity。如果是ListView,可以是ListActivity。
使用XML定义自己的Activity的Layout。其中可以包括ListView,GridView,Gallery。
使用XML定义自己的子Item的视图。也可以不用XML来定义子视图,使用代码也可以。比如说就一个ImageView,那么就没有必要使用XML了。
实现自定义Adapter。基类一般是BaseAdapter。在这个类中必须实现函数getCount,getItem,getPosition和getView函数。一般来说,数据也在这个类中初始化。可以仿照ArrayAdapter来实现。
在Activity的onCreate函数中获取ListView,GridView或者Gallery的实例,然后设置好自己定义的Adapter。
10.开发中的一个小技巧
一般来说ListView,GridView和Gallery只要实现点击事件就可以了,在这个callback函数中会返回点击item的序列号。根据序列号就可以获取相应的数据。但是如果存在Button或者ImageButton。点击事件不再会激发。反而会激发Button的OnClickListener事件。但是在OnClickListener回调函数中不会有Item的序列号,所以我们没有办法获取点击按钮对应的数据。怎么办?
这里需要提到View的一个函数叫getTag和setTag。这个函数使得控件可以跟随一个自定义的数据。也就是在初始化的时候调用setTag来设置控件的跟随数据,然后在需要的时候调用getTag来获取。
比如说XBtnList,我们可以把歌曲名的TextView控件实例设置给ImageButton,在OnClickListener回调函数中调红getTag来获取textView,通过TextView的getText函数就可以获取到对应的歌曲名。
其实convertView也使用了这个技巧。大家可以看代码来体会。
11.示例代码
存在五个示例代码,分别是XVideo,XAudio,XPictures,XBtnList和XImageGrid。界面上比较复杂的是XBtnList。比较简单的,而且不牵扯到其他技术的是XImageGrid。所以我们看这个实例。其他的基本类似。
11.1.继承Activity定义自己的Activity
publicclassXImageGridextendsActivity{
GridViewmGrid;
//显示图片的资源名(这里是为了简单起见)
intmImages[]={R.drawable.img01,...,R.drawable.img25};
//显示的文字
StringmTexts[]={"img01",..."img25"};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.xgrid);
mGrid=(GridView)findViewById(R.id.grdGrid);
mGrid.setAdapter(newXImageTextAdapter(this));
}
publicclassXImageTextAdapterextendsBaseAdapter{
...
}//endclassXImageTextAdapter
}
11.2.使用XML定义自己的Activity的Layout
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<GridViewandroid:id="@+id/grdGrid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:columnwidth="64dp"
android:stretchMode="columnWidth"
android:gravity="center"/>
</LinearLayout>
11.3.使用XML定义自己的子Item的视图
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:id="@+id/imgGrid"
android:layout_width="64dip"
android:layout_height="64dip"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/txtGridName"
android:layout_width="64dip"
android:layout_height="24dip"
android:text="00:03:00:00"
android:gravity="center"/>
</LinearLayout>
11.4.实现自定义Adapter。一般基类是BaseAdapter
publicclassXImageTextAdapterextendsBaseAdapter{
protectedContextmContext;
protectedLayoutInflatermInflater;
publicXImageTextAdapter(Contextcontext){
this.mContext=context;
mInflater=LayoutInflater.from(mContext);
}
publicViewgetView(intposition,
ViewconvertView,
ViewGroupparent){
XImageTextDataxdata=null;
if(convertView==null){
convertView=mInflater.inflate(R.layout.xgrid_view,
null);
xdata=newXImageTextData();
xdata.mImageView=
(ImageView)convertView.findViewById(R.id.imgGrid);
xdata.mTextView=
(TextView)convertView.findViewById(R.id.txtGridName);
convertView.setTag(xdata);
}else{
xdata=(XImageTextData)convertView.getTag();
}
//初始话Image的显示图片
xdata.mImageView.setImageResource(mImages[position]);
//初始话TextView的显示字符串
xdata.mTextView.setText(mTexts[position]);
returnconvertView;
}
publicfinalintgetCount(){
returnmImages.length;//returncount
}
publicfinalObjectgetItem(intposition){
returnmImages[position];
}
publicfinallonggetItemId(intposition){
returnposition;
}
protectedclassXImageTextData{
ImageViewmImageView;
TextViewmTextView;
}
}
11.5.在onCreate函数中获取GridView实例,设置好自己定义的Adapter
mGrid=(GridView)findViewById(R.id.grdGrid);
mGrid.setAdapter(newXImageTextAdapter(this));
12.总结
Android提供了非常好的设计模式让开发者可以非常方面的开发出非常复杂的界面。在ListView,GridView和Gallery方面,只要实现好自己的Adapter就可以了。而Adapter的实现主要是实现getCount,getItem和getView函数。getCount返回数据的节点个数,getItem返回指定节点的数据,getView返回子Item视图的显示。
[color=violet][/color]
前几篇介绍了设计模式的特性并且详细讲解了4种创建型模式,创建型模式是负责如何产生对象实例的,接下来讲讲结构型模式。结构型模式是解析类和对象的内部结构和外部组合,通过优化程序结构解决模块之间的耦合问题。