Android学习笔记(十八):ListView和RatingBar

学习笔记(十七)中,我们对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开发相关文章

posted on 2015-03-28 00:01  troyjie  阅读(329)  评论(0编辑  收藏  举报