zoukankan      html  css  js  c++  java
  • 安卓开发笔记——打造万能适配器(Adapter)

    为什么要打造万能适配器?

    在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需要,因此我们时常要去继承BaseAdapter类去实现一个自定义的适配器来满足我们的场景需要。

    如果你是开发一个简单点的APP还好,可能ListView和GridView的数量不会太多,我们只要去写几个BaseAdapter实现类就可以了。

    但如果有一天,你需要开发一个APP里面具有几十个ListView或者GridView的子页面,此时的你该怎么办?每个ListView或者GridView都去写一个适配的Adatper类吗?

    当然你如果想做蛮牛不嫌累的话也不是不可以,但如果有办法可以让自己减少很多工作量,避免做重复无意义劳动,何乐而不为呢?

    万能适配器思想?

    软件设计模式:模板方法模式(有兴趣了解的朋友,可以参考看下我之前写过的博文《软件设计模式之模板方法模式(JAVA)》)

    其实解决问题的核心思想很简单,一句话:抽取重复代码!

    我们在继承BaseAdapter类时,都需要去实现它里面的抽象方法(getCount, getItem, getItemId, getView),其中除了getView这个方法里需要实现的代码不同,其他的都一样。

    而这个getView方法里,我们考虑到性能的问题,我们经常会引入一个ViewHolder类(关于不清楚ViewHolder的朋友可以看看我之前写过的博文《安卓开发笔记——ListView加载性能优化ViewHolder》),尽可能的去节省资源。

    那么解决问题的思路就出来了,我们可以把这个适配器抽取成2部分:

    第一部分是解决(getCount, getItem, getItemId)方法里重复代码的问题。

    第二部分是分离getView方法里使用到的ViewHolder,把它单独抽取出来成一个独立的类,利用键值对Key=>Value的方法,以控件ID去寻找对应的View对象。

    如果你看完以上这些感觉已经云里来雾里去,没关系,接下去我们用代码说话。

    传统适配器的实现方式:

     1 package com.example.listviewtest;
     2 
     3 import java.util.List;
     4 
     5 import android.content.Context;
     6 import android.view.LayoutInflater;
     7 import android.view.View;
     8 import android.view.ViewGroup;
     9 import android.widget.BaseAdapter;
    10 import android.widget.ImageView;
    11 import android.widget.TextView;
    12 
    13 /**
    14  * 传统适配器Adapter写法
    15  * 
    16  * @author Balla_兔子
    17  * 
    18  */
    19 public class MyAdapter extends BaseAdapter {
    20     private LayoutInflater layoutInflater;
    21     private List<User> data;
    22 
    23     public MyAdapter(Context context, List<User> data, int layoutId) {
    24         //this.context = context;
    25         this.layoutInflater = LayoutInflater.from(context);
    26         this.data = data;
    27         //this.layoutId = layoutId;
    28     }
    29 
    30     @Override
    31     public int getCount() {
    32         return data.size();
    33     }
    34 
    35     @Override
    36     public Object getItem(int position) {
    37         return data.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         ViewHolder viewHolder = null;
    48         if (convertView == null) {
    49             convertView = layoutInflater.inflate(R.layout.listview_item, parent, false);
    50             viewHolder = new ViewHolder();
    51             viewHolder.iv_image = (ImageView) convertView.findViewById(R.id.iv_image);
    52             viewHolder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
    53             viewHolder.tv_phone = (TextView) convertView.findViewById(R.id.tv_phone);
    54             convertView.setTag(viewHolder);
    55         } else {
    56             viewHolder = (ViewHolder) convertView.getTag();
    57         }
    58 
    59         User user = data.get(position);
    60         viewHolder.iv_image.setImageResource(R.drawable.ic_launcher);
    61         viewHolder.tv_name.setText(user.getName());
    62         viewHolder.tv_phone.setText(user.getPhone());
    63 
    64         return convertView;
    65     }
    66 
    67     private class ViewHolder {
    68         ImageView iv_image;
    69         TextView tv_name;
    70         TextView tv_phone;
    71     }
    72 
    73 }
    View Code

     从上面的代码就可以感受到,如果我们去编写多个适配器Adapter的时候,那么我们就势必要去写多个ViewHolder和重复的去写(getCount, getItem, getItemId)方法,而ViewHolder里面常用的控件View也就无非那几种,而那三个方法里(getCount, getItem, getItemId)的代码也是固定不变的,所以重复代码量非常的多,我们应该把它们抽取出来。

    万能适配器实现

    1、首先我们先来分离这个ViewHolder,其实核心代码并没有改变,只是把传统ViewHolder给做的事情给分离出来罢了。

     1 package com.example.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.util.SparseArray;
     5 import android.view.LayoutInflater;
     6 import android.view.View;
     7 import android.view.ViewGroup;
     8 
     9 /**
    10  * ViewHolder集合类
    11  * 
    12  * @author Balla_兔子
    13  * 
    14  */
    15 public class CommonViewHolder {
    16     private SparseArray<View> sparseArray;
    17     private View convertView;
    18     private int position;
    19 
    20     // 构造方法,完成传统Adapter里的创建convertView对象
    21     public CommonViewHolder(Context context, View convertView, int layoutId, ViewGroup parent, int position) {
    22         this.position = position;
    23         this.sparseArray = new SparseArray<View>();
    24         this.convertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
    25         this.convertView.setTag(this);
    26 
    27     }
    28 
    29     // 入口方法,完成传统Adapter里面实例化ViewHolder对象工作
    30     public static CommonViewHolder getCommonViewHolder(Context context, View convertView, int layoutId, ViewGroup parent, int position) {
    31         if (convertView == null) {
    32             return new CommonViewHolder(context, convertView, layoutId, parent, position);
    33         } else {
    34             CommonViewHolder commonViewHolder = (CommonViewHolder) convertView.getTag();
    35             //特别需要注意的一点,由于ListView的复用,比如屏幕只显示5个Item,那么当下拉到第6个时会复用第1个的Item,所以这边需要更新position    
    36             commonViewHolder.position = position;
    37             return commonViewHolder;
    38         }
    39     }
    40 
    41     //根据控件Id获取对应View对象
    42     public <T extends View> T getView(int viewId) {
    43         View view = sparseArray.get(viewId);
    44         if (view == null) {
    45             view = convertView.findViewById(viewId);
    46             sparseArray.put(viewId, view);
    47         }
    48         return (T) view;
    49     }
    50     
    51     //用于返回设置好的ConvertView对象
    52     public View getConvertView(){
    53         return convertView;
    54     }
    55 
    56 }

    这里我们提供了一个入口方法getCommonViewHolder来得到一个ViewHolder的实例对象,若实例不存在,我们去创建并设置Tag保存,这点和先前的ViewHolder所做的事情是一样的。

    由于所有的控件都是View的子类,这里提供了一个getView来获取各控件的对象,在我们需要使用的时候强转成我们所需要的控件类型就可以了,这里提供了一个类似Map的集合SparseArray,这个类和Map一样是利用Key=>Value来存取对象的,不同的是这里的Key是整型变量。

    下面是SpareseArray源码中对其的介绍:

    SparseArray是Android为<Integer,Object>类型的HashMap专门写的类,目的是为了提供效率,其核心算法是折半查找,其用法和Map无两异。

    2、再来分离下BaseAdapter,除getView这个方法会有一些不同,其他的代码其实每次书写都是一样的,我们可以自己写一个抽象类把它们都给实现了,只留getView最关键核心的代码部分给用户实现。由于方法操作,我们这里利用泛型<T>

     1 package com.example.listviewtest;
     2 
     3 import java.util.List;
     4 
     5 import android.content.Context;
     6 import android.view.LayoutInflater;
     7 import android.view.View;
     8 import android.view.ViewGroup;
     9 import android.widget.BaseAdapter;
    10 import android.widget.ImageView;
    11 import android.widget.TextView;
    12 
    13 /**
    14  * 通用适配器Adapter写法
    15  * 
    16  * @author Balla_兔子
    17  * @param <T>
    18  * 
    19  */
    20 public abstract class CommonAdapter<T> extends BaseAdapter {
    21     //为了使得子类可以访问,这里修改包访问级别
    22     protected Context context;
    23     protected LayoutInflater layoutInflater;
    24     protected List<T> data;
    25     protected int layoutId;
    26 
    27     public CommonAdapter(Context context, List<T> data, int layoutId) {
    28         this.context = context;
    29         this.layoutInflater = LayoutInflater.from(context);
    30         this.data = data;
    31         this.layoutId = layoutId;
    32     }
    33 
    34     @Override
    35     public int getCount() {
    36         return data.size();
    37     }
    38 
    39     @Override
    40     public Object getItem(int position) {
    41         return data.get(position);
    42     }
    43 
    44     @Override
    45     public long getItemId(int position) {
    46         return position;
    47     }
    48 
    49     @Override
    50     public View getView(int position, View convertView, ViewGroup parent) {
    51         //获取ViewHolder对象
    52         CommonViewHolder myViewHolder = new CommonViewHolder(context, convertView, layoutId, parent, position);
    53         //需要用户复写的方法,设置所对于的View所对应的数据
    54         setConverView(myViewHolder,data.get(position));
    55         return myViewHolder.getConvertView();
    56     }
    57 
    58     //用户需要实现的方法
    59     public abstract void setConverView(CommonViewHolder myViewHolder, T t);
    60 
    61 }

    完成上面两部分的分离后,我们看看现在的适配器代码编程什么样子

     1 package com.example.listviewtest;
     2 
     3 import java.util.List;
     4 
     5 import android.content.Context;
     6 import android.widget.ImageView;
     7 import android.widget.TextView;
     8 
     9 /**
    10  * 万能适配器Adapter写法
    11  * 
    12  * @author Balla_兔子
    13  * 
    14  */
    15 public class MyAdapter extends CommonAdapter<User> {
    16 
    17     public MyAdapter(Context context, List<User> data, int layoutId) {
    18         super(context, data, layoutId);
    19     }
    20 
    21     @Override
    22     public void setConverView(CommonViewHolder myViewHolder, User user) {
    23         ((ImageView) myViewHolder.getView(R.id.iv_image)).setImageResource(R.drawable.ic_launcher);
    24         ((TextView) myViewHolder.getView(R.id.tv_name)).setText(user.getName());
    25         ((TextView) myViewHolder.getView(R.id.tv_phone)).setText(user.getPhone());
    26     }
    27 }

    很明显,代码量减少了近2/3,而且是一劳永逸,CommonAdapter和CommonViewHolder再也不需要变动了,需要什么我们往里面直接加就可以了,这样让我们可以更为专注的去实现核心代码。当然还可以更简化一点,把这些ViewHolder.getView和setText,setImage等方法再一次封装,变成只传递控件Id和对应数据就够了,这样一来我们连类都不需要写了,直接用new对象去写个内部类实现就可以了。

    附上主MainActivity代码:

     1 package com.example.listviewtest;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 import android.app.Activity;
     7 import android.os.Bundle;
     8 import android.widget.ListView;
     9 
    10 public class MainActivity extends Activity {
    11     
    12     private ListView listView;
    13     private MyAdapter adapter;
    14     private List<User> list;
    15     @Override
    16     protected void onCreate(Bundle savedInstanceState) {
    17         super.onCreate(savedInstanceState);
    18         setContentView(R.layout.activity_main);
    19         //初始化
    20         listView=(ListView) findViewById(R.id.listview);
    21         list=new ArrayList<User>();
    22         
    23         
    24         //模拟数据源
    25         for(int i=0;i<10;i++){
    26             User user=new User();
    27             user.setName("用户"+i);
    28             user.setPhone("10000"+i);
    29             list.add(user);
    30         }
    31         
    32         adapter=new MyAdapter(MainActivity.this, list,R.layout.listview_item);
    33         
    34         listView.setAdapter(adapter);
    35     
    36 
    37     }
    38 
    39 }
    View Code

     就像这样,以后如果需要使用适配器Adapter就不需要再去继承BaseAdapter了,直接继承CommonAdapter配合CommonViewHolder就可以了。

  • 相关阅读:
    Java基础复习(1)
    mybatis中Oracle分页语句的写法
    Spring Security 入门原理及实战
    Java中的基本类型和包装类型区别
    Apache Shiro简单介绍
    linux常用命令介绍
    Spring Cloud的简单介绍
    服务端跳转和客户端跳转
    使用ajax向后台发送请求跳转页面无效的原因
    js css html加载顺序
  • 原文地址:https://www.cnblogs.com/lichenwei/p/4436489.html
Copyright © 2011-2022 走看看