BaseAdapter是一种使用频率较高的适配器,因为它可以通过自定义最大程度扩展满足各种情景下的使用。我们不仅需要知道适配器的使用,进一步我们也需要了解适配器的原理。
问题是最好的学习方式,下面主要讨论这么几个问题:
Q1.ListView中每个Item的创建
Q2.ListView中Item的复用
Q3.ListView中屏幕显示的Item与复用生成Item之间的关系
首先写一个简单的demo.
1 package com.aellenlei.baseadapterdemo; 2 3 import android.support.v7.app.AppCompatActivity; 4 import android.os.Bundle; 5 import android.widget.ListView; 6 7 import java.util.ArrayList; 8 import java.util.List; 9 10 public class MainActivity extends AppCompatActivity { 11 12 @Override 13 protected void onCreate(Bundle savedInstanceState) { 14 super.onCreate(savedInstanceState); 15 setContentView(R.layout.activity_main); 16 17 //1.findViewById 18 ListView listView = (ListView) findViewById(R.id.listView); 19 20 //2.初始化数据源 21 List<ItemBean> list = new ArrayList<>(); 22 for (int i = 0; i < 5; i++) { 23 ItemBean itemBean = new ItemBean(); 24 itemBean.postion = i; 25 itemBean.title = "title " + i; 26 itemBean.content = "content " + i; 27 list.add(itemBean); 28 } 29 30 //3.初始化适配器 31 ItemBeanAdapter itemBeanAdapter = new ItemBeanAdapter(list, getApplicationContext()); 32 33 //4.ListView绑定适配器 34 listView.setAdapter(itemBeanAdapter); 35 } 36 }
1 package com.aellenlei.baseadapterdemo; 2 3 import android.content.Context; 4 import android.util.Log; 5 import android.view.LayoutInflater; 6 import android.view.View; 7 import android.view.ViewGroup; 8 import android.widget.BaseAdapter; 9 import android.widget.CheckBox; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 import java.util.List; 14 15 /** 16 * User AellenLei 17 * NAME ItemBeanAdapter 18 * DATE 2016/3/7 19 */ 20 public class ItemBeanAdapter extends BaseAdapter { 21 22 private List<ItemBean> mData; 23 private Context mContext; 24 25 public ItemBeanAdapter(List<ItemBean> mData, Context mContext) { 26 this.mData = mData; 27 this.mContext = mContext; 28 } 29 30 @Override 31 public int getCount() { 32 return mData == null ? 0 : mData.size(); 33 } 34 35 @Override 36 public ItemBean getItem(int position) { 37 return mData.get(position); 38 } 39 40 @Override 41 public long getItemId(int position) { 42 return position; 43 } 44 45 @Override 46 public View getView(int position, View convertView, ViewGroup parent) { 47 48 Log.d("msg", position + "," + getItem(position).postion + "," + getItem(position).title + ". " 49 + ((convertView == null) ? ("covertView = null") : 50 (((TextView) convertView.findViewById(R.id.item_title)).getText().toString())) 51 ); 52 53 54 View ret; 55 56 if (convertView != null) { 57 ret = convertView; 58 } else { 59 ret = LayoutInflater.from(mContext).inflate(R.layout.item, null); 60 ViewHolder holder = new ViewHolder(); 61 holder.itemIcon = (ImageView) ret.findViewById(R.id.item_icon); 62 holder.itemTitle = (TextView) ret.findViewById(R.id.item_title); 63 holder.itemContent = (TextView) ret.findViewById(R.id.item_content); 64 holder.itemDate = (TextView) ret.findViewById(R.id.item_date); 65 holder.itemChecked = (CheckBox) ret.findViewById(R.id.item_check); 66 ret.setTag(holder); 67 } 68 69 ViewHolder viewHolder = (ViewHolder) ret.getTag(); 70 71 ItemBean itemBean = getItem(position); 72 73 viewHolder.itemIcon.setImageResource(R.mipmap.ic_launcher); 74 viewHolder.itemTitle.setText(itemBean.title); 75 viewHolder.itemContent.setText(itemBean.content); 76 viewHolder.itemDate.setText("yyyy-MM-dd"); 77 viewHolder.itemChecked.setChecked(itemBean.check); 78 79 return ret; 80 } 81 82 private static class ViewHolder { 83 private ImageView itemIcon; 84 private TextView itemTitle; 85 private TextView itemContent; 86 private TextView itemDate; 87 private CheckBox itemChecked; 88 } 89 }
1 package com.aellenlei.baseadapterdemo; 2 3 /** 4 * User AellenLei 5 * NAME ItemBean 6 * DATE 2016/3/7 7 */ 8 public class ItemBean { 9 public int postion; 10 public String url; 11 public String title; 12 public String content; 13 public String date; 14 public boolean check; 15 }
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".MainActivity"> 11 12 <ListView 13 android:id="@+id/listView" 14 android:layout_width="match_parent" 15 android:layout_height="match_parent"/> 16 </RelativeLayout>
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:padding="8dp"> 6 7 <ImageView 8 android:id="@+id/item_icon" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:src="@mipmap/ic_launcher" 12 android:padding="4dp"/> 13 14 <TextView 15 android:id="@+id/item_title" 16 android:layout_width="wrap_content" 17 android:layout_height="wrap_content" 18 android:textSize="20sp" 19 android:textStyle="bold" 20 android:layout_toRightOf="@id/item_icon" 21 android:layout_alignTop="@id/item_icon"/> 22 <TextView 23 android:id="@+id/item_content" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:textSize="16sp" 27 android:layout_below="@id/item_title" 28 android:layout_toRightOf="@id/item_icon" 29 android:layout_alignBottom="@id/item_icon"/> 30 31 <TextView 32 android:id="@+id/item_date" 33 android:layout_width="wrap_content" 34 android:layout_height="wrap_content" 35 android:layout_toRightOf="@id/item_title" 36 android:layout_alignParentRight="true" 37 android:gravity="right"/> 38 <CheckBox 39 android:id="@+id/item_check" 40 android:layout_width="wrap_content" 41 android:layout_height="wrap_content" 42 android:text="@null" 43 android:layout_below="@id/item_date" 44 android:layout_alignParentRight="true" 45 android:gravity="center" 46 android:layout_alignBottom="@id/item_content"/> 47 </RelativeLayout>
由于该Demo比较简单,不需多讲相信都可以看懂。
ItemBeanAdapter.java的getView方法中有这么一句:
分别打印的是ListView中每一个Item在ListView中的位置(默认从0开始,下同),该Item显示的数据源中指定位置数据的poistion和title,convertView是否为空若不为空打印convertView之前显示的title.
当数据源中的数据条数为5时,Logcat的日志:
UI:
当数据源中的数据条数为10是,Logcat的日志:
UI:
根据这两种情况的测试,可以大概回答第一个问题:
Q1.ListView中每个Item的创建
A1.Adapter第一次创建的Items的数量是由手机屏幕的大小(可测试)和数据源数据的条数来决定的,也就是屏幕实际显示多少个Item就创建几个item的,不多创建新的item也不少创建新的item,items数量是屏幕实际显示数目的取整。将下面的完整结合来看,第一次创建的Item是最基本的Item,它的数量是确定的,以后新的item无论是向上滑动出现还是向下滑动出现都是复用第一次创建的items中的某个item。
Q2.ListView中Item的复用
A2:最核心的代码就是Adapter中的getView方法,它返回的是一个已经绑定好数据的view,而系统仅仅只是将这个view在屏幕的指定位置绘制出来。
代码不是固定死的,当然你可以有自己的写法,但是原理总是相同的:
A2:当ListView第一次创建一屏幕的items时,covertView始终为null(代码测试很容易得出),所以当covertView为空时,就需要将第一次创建一屏幕的items的每个item“初始化”,这里的“初始化”是将covertView和ViewHolder绑定起来,注意不论是将covertView和ViewHolder绑定起来还是ret和ViewHolder绑定起来,它们的本质是一样的,最后返回值是已经与ViewHolder绑定的View视图,当掌握了covertView的复用写法,可以说是基本上item的复用的写法也掌握了。
注意下面一种情况,当ListView向上滑动,且item0完全不见,item7和item8出现(下图item8已经出现,只是没有完全显示)的情况:
此时Logcat打印的日志:
根据之前的第一次创建items打印的日志比较:item7仍然是新创建的,但是item8却是复用的,item8复用的是item0(完全根据日志得出的)。(PS 可能此处有疑问,下面会分析)
A2:当listView向上滑动或者是向下滑动的时候,此时可能会出现item复用的情况(注意此时可能会出现复用的情况,不一定或出现哦)。若covertView不为空,就可以之前在该covertView初始化或复用中通过getTag方法,取出与之绑定的ViewHolder,从而实现减少findViewById的时间,findViewById是需要耗费时间的,当listView显示大量的数据,此时的findViewByid可以极大的提高效率。
最后分析总结前面的,可以回答第三个问题:
Q3.ListView中屏幕显示的Item与复用生成Item之间的关系
A3:ListView实际创建item的数量是由手机屏幕的大小和数据源的数据数量来决定的,准确的将这是不准确的或是错误的。
ListView在整个复用过程中本质上实际创建item的数量(这里所指的全部是最原始最本质的item)是由手机屏幕的大小、数据源的数据数量和每个item实际的大小来决定的(当然这里不考虑其他更为负责的情况,而是假定每个item的大小相同)。
用可以唯一衡量确定的话说是:本质上items的数量是当第一个item完全消失后,此时Adapter总共创建的items数量,从本质上来说,这就是ListView在整个复用过程中复用的item的数量。假如从0,1,...,n-1(n为最原始的item数量)来看,当ListView向上滑动时,复用tem的顺序是按顺序复用0,1,...,n-1,每次复用一个;当ListView向下滑动时,复用的顺序是按照逆序的,从n-1,n-2,..,0,也是一个一个复用的。
当然还有更为复杂的情况或者说从更为本质也就是源码的角度分析,这里暂不考虑,而是从一种最为表象或者最最最基本最最简单来分析ListView与Adapter。