对于刚接触ListView组件的朋友来说,虽然很多教材或者文章都介绍了利用ListView实现列表的一般步骤,大家也都可以依葫芦画瓢去实现简单的列表效果,但同时也会产生些许疑惑,比如何adapter,其作用是什么?实现的getView方法被谁调用,何时被调用等等,这里通过一个小的案例来研究getView方法的调用和传参机制,帮助大家更好的理解和应用。
为了研究getView方法的调用机制,这里首先使用ListView组件完成一个商品列表案例,效果如下:
Activity源码如下:
public class MainActivity extends AppCompatActivity{
ListView lv;
//需要适配的数据
private String[] titles = { "桌子", "苹果", "蛋糕", "线衣", "猕猴桃", "围巾",
"桌子", "苹果", "蛋糕", "线衣", "猕猴桃", "围巾"};
private String[] prices = { "1800元", "10元/kg", "300元", "350元", "10元/kg", "280元",
"1800元", "10元/kg", "300元", "350元", "10元/kg", "280元"};
//图片集合
private int[] icons = {R.drawable.table,R.drawable.apple,R.drawable.cake,
R.drawable.wireclothes,R.drawable.kiwifruit,R.drawable.scarf,
R.drawable.table,R.drawable.apple,R.drawable.cake,
R.drawable.wireclothes,R.drawable.kiwifruit,R.drawable.scarf};
//保存添加的商品个数
private int[] no = new int[]{0,0,0,0,0,0,0,0,0,0,0,0};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//初始化组件对象
initView();
//创建适配对象
MyAdapter myAdapter = new MyAdapter();
//绑定到ListView对象
lv.setAdapter(myAdapter);
}
//将xml组件转换成组件对象
private void initView(){
lv = (ListView)findViewById(R.id.lv);
}
class MyAdapter extends BaseAdapter{
class ItemComs{
ImageView iv;
TextView tvTop;
TextView tvBottom;
TextView tvNo;
Button btJ;
Button btZ;
}
@Override
public int getCount() {
return titles.length;
}
@Override
public Object getItem(int position) {
return titles[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ItemComs ics = new ItemComs();
if(convertView == null){
//Log.e("position",position+"");
convertView = View.inflate(MainActivity.this,R.layout.item_view,null);
findViews(ics,convertView);
convertView.setTag(ics);
}
else{
ics = (ItemComs) convertView.getTag();
}
setOnEvents(ics,position);
initViews(ics,position);
return convertView;
}
//将layouy_item组件转换成对象
private void findViews(ItemComs ics,View view){
ics.iv = (ImageView)view.findViewById(R.id.iv);
ics.tvTop = (TextView) view.findViewById(R.id.tvTop);
ics.tvBottom = (TextView)view.findViewById(R.id.tvBottom);
ics.tvNo = (TextView)view.findViewById(R.id.tvNo);
ics.btJ = (Button)view.findViewById(R.id.btJ);
ics.btZ = (Button)view.findViewById(R.id.btZ);
}
//设置组件内容
private void initViews(ItemComs ics,int position){
ics.iv.setImageResource(icons[position]);
ics.tvTop.setText(titles[position]);
ics.tvBottom.setText(prices[position]);
ics.tvNo.setText(no[position]+"");
}
//给“+”“-”按钮绑定事件
private void setOnEvents(final ItemComs ics,final int position){
ics.btJ.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(no[position]<=0){
Toast.makeText(MainActivity.this,"请添加商品",Toast.LENGTH_SHORT).show();
}
else{
no[position] -= 1;
ics.tvNo.setText(no[position]+"");
}
}
});
ics.btZ.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
no[position] += 1;
ics.tvNo.setText(no[position]+"");
}
});
}
}
}
BaseAdapter是最万能最好用的数据适配器之一,可以给ListView、Spinner、GridView多种控件填充数据。通俗的说,适配器的作用就是在数据和视图之间建立一种桥梁,类似一个转换器,能够将复杂的数据转换成用户可以接受的方式进行呈现。本案例的核心就是继承BaseAdapter类并实现其4个抽象方法:
1.int getCount() 填充的item个数
2.Object getItem(int position) 指定索引对应的item数据项
3.long getItemId(int position) 指定索引对应item的id值
4.View getView(final int position, View convertView, ViewGroup parent) 填充每个item的可视内容并返回
其中最重要最核心的就是getView方法,它负责给每个item填充内容,并且返回视图对象。这里重点罗列下,初写案例时对getView方法的一些疑惑:
1.getView方法由谁调用,何时被调用?
2.参数positon、convertView 分别代表什么意思?
3.View.inflate静态方法的作用?
4.convertView.setTag();getTag()方法的作用?
为了更清楚的了解运行机制,这里在getView方法中添加一处打印,输出position及convertView 。
程序运行初始界面及结果:
通过运行结果可以清楚的看到,当运行程序时,由于ListView控件的可视区域内最大能够容纳7个item,所以一共调用了7次getView方法,每次绘制出一个item,并且position代表着每个item的索引号,从0开始。
继续实现,滑动屏幕看效果:
通过gif图可以很清楚的发现,由于ListView的可视区域是固定的,最多能够显示7个item,因此在滑动屏幕时,肯定会出现某些item被划出屏幕,同时又有item被划入屏幕显示的现象,那么每当某个item进入到可视区域,就会自动调用getView方法来填充数据并绘制。并且convertView这个引用在屏幕滑动过程中不再被赋值了,也就是说除了界面启动时初次绘制ListView的7个item区域时通过View.inflate分别获取到layout_item布局对象后,再也不用被赋值了。
因此得出结论:
1.getView方法是由系统自动回调的方法,每当可视区域内需要刷新一个item时就会被调用,用来填充item内容、绑定事件等其他操作。
2.参数position是系统回调getView方法时自动传入的,代表当前刷新的item的索引号,下标从0开始。
3.界面启动时,自动调用getView方法传入的convertView均为null; 根据代码逻辑
if(convertView == null){ convertView = View.inflate(MainActivity.this,R.layout.item_view,null); findViews(ics,convertView); convertView.setTag(ics); }
当convertView为null时就是调用View.inflate给其赋值。(关于这一块的理解见后文)
4.每当某个item进入到可视区域,就会自动调用getView方法来填充数据并绘制View。
5.convertView的理解,就拿本案例来说,如果把ListView区域理解成一片试验田基地,那么这个基地最大能开发7个试验田。
启动程序——就好像在规划和搭建试验基地,这时候试验田只是规划了区域,还没有正式开开挖。
初始界面——7个item显示完成,就好像完成了7个试验田的挖掘工作,convertView就可以理解成7个试验田的标识牌。因此第一次调用getView方法时convertView都为null;然后通过View.inflate将layout_item转换成View对象,就好比挖掘完成,然后贴上convertView标识牌。所以7块试验田,一共挖掘7次,convertView被赋值7次,分别代表不同区域的itemlayout信息。
界面滚动——完成启动,试验田已经开挖完成,当界面滚动时虽然会有新的item进入旧的item出去,不管你有多少个item但实验田数量依旧是7个,只是填充不同的内容即可,就好像这片田种完花生,然后收获了再种棉花,田还是一片田,因此不用再挖掘(当然你要在挖也行,只是代码反复执行View.inflate降低执行效率),只需将数据填充进layout_item各个控件即可。
根据刚才提出的试验田原理,我们结合getView源码进行最后分析:
ListView——实验基地
layout_item——实验田区布局
class ItemComs——实验田区内部控件集合
View.inflate——挖掘实验田,无需重复挖掘
convertView——实验田区标识,保存实验田区布局对象信息,通过该对象可以找到具体实验田,进而通过findViewById找到ItemComs各控件
convertView.setTag(ics);——保存ItemComs对象,方便下次绘制item时直接填充内容。
convertView.getTag(ics);——获取ItemComs对象,绘制item时直接填充内容。
真编不下去了,就这样把,有些用语言很好描述的问题,为啥用文字就这么难!