在 学习笔记(十七) 中,我们对ListView做了进一步的探讨,然而给出的例子list中的元素可以有多个widget,并可灵活设置他们的值,但是这些widget之间缺乏互动,而且getView()的调用,需要重刷给list的entry,我们希望能够在entry中触发变化。
本次,我们继续根据《Beginging Android 2》的学习,结合RatingBar,将程序稍微复杂一点。RatingBar看用于媒体库的平级,我们用RatingBar取代了之前例子的图标,当RatingBar设置为三星时,该entry后面的文本改为大写,如果低于三星将恢复原来的小写显示。
例子:自定义数据结构和内部widget的触发处理
1)Android XML文件: 用RatingBar替代之前例子的ImageView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ……>
< RatingBar android:id="@+id/c85_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android: numStars = "3" <!-- 设置三星平级方式-->
android: stepSize = "0.5" <!--step为0.5,也就是允许2.5的星级评比 -->
android: rating = "2"/> <!-- 缺省为2星-->
<TextView android:id="@+id/c85_label"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="10px"
android:textSize="24sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
2)设置自定制数据结构来存储信息,并提供查询信息的方法
在之前的例子中,我们使用了ArrayList<String>来存放每个单元的数据信息,在这个例子中,作为更通用的方式,每个单元信息为我们自定的类RowModel。
class RowModel{
String label; //存储entry的当前文本显示内容,通过调用toString()给出,如果三星将提供大写显示。
float rating = 2.0f; //存储entry的星级数据,对应RatingBar的星级显示
RowModel(String label){
this.label = label;
}
public String toString(){
if(rating >= 3.0){
return label.toUpperCase();
}
return label;
}
}
在我们的主类中,根据自定义的数据结构设置我们的数据信息list,并导入list adapter中,同时我们增加一个方法,根据position(index)来从数据信息中获取该单元的数据。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<RowModel> list = new ArrayList<RowModel>(); //步骤1:list作为数据的存储
for(String s: items){ //步骤2:将String[] items的信息导入list中,这种写法比较特别,我一般会老老实实for(int i =0; i <items.length; i++)的方式来写。
list.add(new RowModel(s));
}
setListAdapter(new RatingAdapter(list)); //步骤3:设置自定制的listadapter(具体在后面处理),并将信息数据list导入其中
}
//根据List的位置,获得具体的list元素,一般add,del,find的处理中,相当于find
private RowModel getModel(int position){
return ((RatingAdapter)getListAdapter()).getItem(position);
}
3)List单元的View和widget信息捆绑,实现快速定位widget
根据之前的学习,为了使程序运行得更有效率,我们会使用setTag的方式,将list单元的UI的View和存储单元UI中widget信息的类捆绑,以便可以快速定位widget。
步骤1:设置存储List单元View中widget的相关类。
其实,我们可以将这些widget信息和2)中的数据信息放在一起,在这个例子中程序会更借鉴,但是这样的处理很不好,我们尽可能把要将UI相关的信息和数据信息放在一起,否则UI修改或者进行尺寸适配时出现麻烦。
private class ViewWrapper{
View base;
RatingBar rate = null;
TextView label = null;
ViewWrapper(View base){
this.base = base;
}
RatingBar getRatingBar(){
if(rate == null)
rate =(RatingBar) base.findViewById(R.id.c85_rating);
return rate;
}
TextView getLabel(){
if(label == null)
label = (TextView)base.findViewById(R.id.c85_label);
return label;
}
}
步骤2:List单元View的呈现(getView),并且提供其中widget触发的处理
一个List单元的View对应两个内容,一个是存储的数据,可以通过getModel来获得,另一个是对应的单元UI的widget队形的存储,通过getTag()和setTag(),这个在上一次学习中已经学习了,我们还需要增加View中widget的触发,在这个例子中,当RatingBar的星级出现变化是,可能需要重写刷新后面文章的显示。我们具体看代码:
private class RatingAdapter extends ArrayAdapter<RowModel>{
//步骤2.1:设置构造函数,将数据信息放入ArrayAdapter中,这样可以通过getItem() 获取数据信息,同时也设置layout格式
RatingAdapter(ArrayList<RowModel> list){
super(Chapter8Test5.this,R.layout.entry,list);
}//步骤2.2: 编写ListView中每个单元的呈现
public View getView (int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewWrapper wrapper;
RatingBar ratebar = null;
//步骤2.3:如果没有创建View,根据layout创建之,并将widget的存储类的对象与之捆绑为tag
if(row == null){
LayoutInflater inflater=getLayoutInflater();
row = inflater.inflate(R.layout.entry, parent,false);
wrapper = new ViewWrapper(row);
row.setTag(wrapper);
//步骤2.4:在生成View的时候,添加将widget的触发处理
ratebar = wrapper.getRatingBar();
ratebar. setOnRatingBarChangeListener (new RatingBar. OnRatingBarChangeListener () {
public void onRatingChanged (RatingBar ratingBar, float rating, boolean fromUser) {
//步骤2.4.1:存储变化的数据
Integer index = (Integer)ratingBar.getTag();
RowModel model = getModel(index);
model.rating = rating;
//步骤2.4.2:设置变化
LinearLayout parent = (LinearLayout)ratingBar.getParent();
TextView label = (TextView)parent.findViewById(R.id.c85_label);
label.setText(model.toString());
}
});
}else{ //步骤2.4:利用已有的View,获得相应的widget
wrapper = (ViewWrapper) row.getTag();
ratebar = wrapper.getRatingBar();
}
//步骤2.5:设置显示的内容,同时设置ratingbar捆绑tag为list的位置,因为setTag()是View的方法,因此我们不能降至加在ViewWrapper,所以需要加载ViewWrapper中的widget中,这里选择了ratebar进行捆绑。
RowModel model= getModel(position);
wrapper.getLabel().setText(model.toString());
ratebar. setTag (new Integer(position));
ratebar.setRating(model.rating);
return row;
}
}
我们在这里例子中进行了一个实验,考察什么时候convertView可以为null,一屏可以显示0-8个row,这些list的元素都是null,需要通过程序来创建,然而当我混动屏幕的时候,我想象中,后面的元素第一次也应该为0,但是出乎我的意外,只有position=14的出现row=null。 对于通过scroll屏幕的情况,下一屏Android可能根据第一屏对UI的处理情况进行了处理。 因此 Android对UI的智能处理情况我们不太能把握,因此任何与数据有关,不是纯粹的UI问题的初始赋值的问题,不要只放置在if(row==null)中进行初始处理,否则会引起不可预测的意外 。例如我们将步骤2.5中的ratebar.setTag(new Integer(position))此句放在if(row==null)会得到不正常的结果,因为不是所有的list元素中的该widget都在初始的情况下成功进行了捆绑,所以我们将它放置在外面或者通知方式在if和else的判断中,保证所有情况都覆盖。
ListAdapter:CursorAdapter
一般来讲,我们可以使用ArrayAdapter来适用很多情况,还有其他的Adapter,使用方式类似,但是CursorAdapter有些不一样,通过newView()和bindView(),如果没有创建,使用newView(),然后调用bindView(),如果已经创建,使用bindView()。
相关链接: 我的Andriod开发相关文章