ListView作为Android 开发中使用最多的控件之一,相信很多人对他的用法应该都不陌生,最主要就是,定义好每个Itme的布局,给其设置一个数据适配器(Adapter)就完事,下面来回顾一下ListVew的一般用法。
/**
* Created by 毛麒添 on 2017/1/18 0018.
*/
public class MainActivity extends AppCompatActivity {
private ArrayListdata;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data=new ArrayList();
for (int i=0;i<30;i++){
data.add("listView数据"+i);
}
ListView listView=new ListView(getContext());
listView.setAdapter(new MyAdapter());
}
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return data.size();
}
@Override
public String getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder=null;
if(convertView==null){
//1.加载布局文件
convertView= MyUIUtils.inflate(R.layout.list_home_item);
viewHolder=new ViewHolder();
//2.初始化控件
viewHolder.textView= (TextView) convertView.findViewById(R.id.tv_test);
//3.打一个标记tag
convertView.setTag(viewHolder);
}else {
viewHolder= (ViewHolder) convertView.getTag();
}
//4.根据数据来刷新界面
viewHolder.textView.setText(getItem(position));
return convertView;
}
}
static class ViewHolder{
TextView textView;
}
}
- 如果我们只是给一个ListView设置数据,则写一个上面的数据适配器无可厚非,但是如果在一个项目中有许多的ListView,则给每个ListView都写一个数据适配器,则上面的适配器中必须实现 getCount() getItem(),getItemId(),getView() 方法对每个都要写一次,这样多几次应该会感觉很烦,那有没有可能对这几个方法抽取出来,进行封装,简便要写多个 ListView 数据适配器的烦恼呢?答案是肯定的,下面就开始对ListView的数据适配器进行两层封装,以达到我们的目的。
ListView的第一层封装
- 首先可以对最简单的 getCount(),getItem(),getItemId() 方法进行封装,创建我们自己的父类Adapter,让其继承BaseAdpter.其中getCount(),getItem(),getItemId() 最后设置的返回值一般都是依赖一个List数组,但这个父类是我们定义的,则可以用构造方法来传入List数组,而这时候问题又来了,数组的泛型我们也不知道,这时候可以把泛型定义为Object,但是当我们传入数据的时候则就要将 Object 强转为我们传入数据对应的泛型,其实这里可以直接传入一个大写字母(ArrayList 源码中也是如此定义),代表传入的数据泛型,下面上代码:
/**
* Created by 毛麒添 on 2017/1/21 0021.
* 对Adapter的封装 简化数据适配器的设置
*/
public class MyBaseAdapter extends BaseAdapter {
private ArrayListdata; //T代表泛型,传入的是什么类型就是什么类型
public MyBaseAdapter(ArrayListdata){
this.data=data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public T getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
- 这样当我们使用 MyBaseAdapter 来作为数据适配器的父类,就不用再写 getCount(),getItem(),getItemId()这三个方法了,OK,完成对 ListView 的第一层封装,但是这个我们自定义的父类方法还没完成,接下来就是对getView()的封装。
ListView的第二层封装
- 前面只是对 getCount(), getItem() ,getItemId() 等方法进行了封装,接下来便是对 getView() 方法来进行封装,以前我们写ListView数据适配器的getView()方法一般都是以下四个步骤:
- 1.加载布局文件
- 2.初始化控件
- 3.打一个标记tag
- 4.根据数据来刷新界面
- 无论如何,这四个步骤都是要实现的。而这四个步骤中,都在其中有ViewHolder帮助类来帮助我们实现,所以我们可以考虑在将这四个步骤抽取成为一个工具类,也就是自己定义一个父类holder,在其中实现这几个步骤,进一步进行封装。
import android.view.View;
/**
* Created by 毛麒添 on 2017/1/21 0021.
* getView(); ViewHolder封装
*/
public abstract class BaseHolder {
private final View view;//ListView的item根布局View对象
private T data;
/**获取这个父类holder,就是执行
* 1.加载布局文件
* 2.初始化控件
* 3.打一个标记tag
* 这三个步骤
*/
public BaseHolder(){
view = initView();
//3.打一个标记tag,也就是当前的BaseHolder
view.setTag(this);
}
//设置当前Item的数据
public void setData(T data){
this.data=data;
//获取数据直接将其传递给子类刷新数据
RefreshView(data);
}
//获取当前Item的数据
public T getData(){
return data;
}
/**
* 本身并不知道需要加载什么样的布局和控件,让子类去实现
* 1.加载布局文件
* 2.初始化控件
* @return 返回相应的item根布局View对象
*/
public abstract View initView();
/**
* 获取Item布局对象
* @return 返回item布局对象
*/
public View getItemView(){
return view;
}
//4.根据数据来刷新界面,本身并不知道是什么数据,让子类去实现
public abstract void RefreshView(T data);
}
- 代码中,首选我们实现第一、二个步骤,但是这只是所有布局Item的父类,并不知道每个ListView的Item需要展现什么样的布局,所以定义一个抽象方法 initView()让子类去实现布局的加载与控件的初始化,而且在初始化父类的时候,也就是在构造方法中就调用该方法获取对应的Item的View对象,既然获取了对应的View对象,并且该类就是四个步骤ed封装,也就可以给其设置一个Tag,到此,前三个步骤已经搞定,最后一个步骤是给Item布局设置刷新数据,同理也是定义成抽象方法RefreshView(T data)让子类去实现,再加上两个数据set和get方法。
至此,四个步骤封装完成。 - 接下来搞个子类继承BaseHolder实现未实现的方法,指定特定的ListView的Item需要展示的数据。
/**
* Created by 毛麒添 on 2017/1/21 0021.
* BaseHolder子类
*/
public class MyHolder extends BaseHolder {
private TextView textView;
@Override
public View initView() {
//步骤1.加载布局文件
View view= MyUIUtils.inflate(R.layout.list_home_item);
//步骤2.初始化控件
textView = (TextView) view.findViewById(R.id.tv_test);
return view;
}
//步骤4刷新页面数据
@Override
public void RefreshView(String data) {
textView.setText(data);
}
}
现在,就可以把上面未完成的MyBaseAdapter父类完整实现:
public abstract class MyBaseAdapter extends BaseAdapter {
private ArrayListdata; //T代表泛型,传入的是什么类型就是什么类型
public MyBaseAdapter(ArrayListdata){
this.data=data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public T getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseHolder baseHolder;
if(convertView==null){
/**
* 1.加载布局文件
* 2.初始化控件
* 3.打一个标记tag
*/
baseHolder=getHolder();//由子类返回具体对象
}else {
baseHolder= (BaseHolder) convertView.getTag();
}
//4.根据数据来刷新界面
baseHolder.setData(getItem(position));
//返回item对象
return baseHolder.getItemView();
}
//返回当前页面的holder对象,必须由子类去实现
public abstract BaseHolder getHolder();
}
- 在上面的getView()方法,只要初始化BaseHolder的对象,就已经完成了四个步骤中的前三个,第四个步骤只要将数据设置给BaseHolder的对象也就完成了,对象的初始化也是需要继承该方法的子类去实现,因为每个LsitView的所展示的Item是不同的。
- 终于,对ListView二层封装已经完成,下面我们来使用一发:
public class MainActivity extends AppCompatActivity {
private ArrayListdata;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data=new ArrayList();
for (int i=0;i<30;i++){
data.add("listView数据"+i);
}
ListView listView=new ListView(getContext());
listView.setAdapter(new MyAdapter());
}
class MyAdapter extends MyBaseAdapter{
public MyAdapter(ArrayList data) {
super(data);
}
@Override
public BaseHolder getHolder() {
return new MyHolder();
}
}
- 好了,以后我们在使用ListView,只需要写一个类继承BaseHolder,然后按照我们的数据获取初始化布局和控件,然后设置数据,只需要在数据适配器中初始化该类就完事。
最后
抽取相同的部分,封装,应该相当于搭建了一个小框架吧,也让我对适配器有了更进一步的认识。如果有哪些地方写得不对,欢迎大家给我提出来纠正,让我们一同进步。