zoukankan      html  css  js  c++  java
  • ListView ,GridView 通用适配器

    前言


      接近半年的时间没有写博客了,今年公司的项目有点多,比较忙,没时间写,这是其一。其次是,这半年来,有时间的时候,
    我都会看看自己以前写的博客,也许是以前刚刚写博客,经验不足,感觉写出来的博客质量很不好,而最近,也经常在网上看别人的博客
    学到到了很多,趁最近项目都差不多收尾了,就写写今年下半年的第一篇博客...

      从事Android开发工作有几年时间了,我发现做手机应用的话,基本都离不开ListView,GridView,这些控件,尤其是ListView
    我曾开发过一个项目,70%的都是列表,光写adapter,就接近上百个,写到最后,都感觉已经麻木了...比如下面这个例子

    MainActivity页面

     1 package com.example.huangjialin.listviewadapter;
     2 
     3 import android.app.Activity;
     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 Activity {
    11     private ListView listview;
    12     private List<String> stringList;
    13     private TestAdapter adapter;
    14 
    15 
    16     @Override
    17     protected void onCreate(Bundle savedInstanceState) {
    18         super.onCreate(savedInstanceState);
    19         setContentView(R.layout.activity_main);
    20 
    21         stringList = new ArrayList<String>();
    22         for (int i = 0; i < 50; i++) {
    23             stringList.add("测试" + i);
    24         }
    25 
    26         listview = (ListView) findViewById(R.id.listview);
    27         adapter = new TestAdapter(this, stringList);
    28         listview.setAdapter(adapter);
    29     }
    30 }

    主页面布局

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:id="@+id/activity_main"
     4     android:layout_width="match_parent"
     5     android:layout_height="match_parent"
     6     >
     7 
     8     <ListView
     9         android:id="@+id/listview"
    10         android:layout_width="match_parent"
    11         android:layout_height="match_parent"
    12         />
    13 </RelativeLayout>

    TestAdapter页面

     1 package com.example.huangjialin.listviewadapter;
     2 import android.content.Context;
     3 import android.view.LayoutInflater;
     4 import android.view.View;
     5 import android.view.ViewGroup;
     6 import android.widget.BaseAdapter;
     7 import android.widget.TextView;
     8 import java.util.List;
     9 
    10 /**
    11  * Created by huangjialin on 2017/9/12.
    12  */
    13 public class TestAdapter extends BaseAdapter {
    14     private List<String> stringList;
    15     private Context mContext;
    16 
    17     public TestAdapter(Context context, List<String> stringList) {
    18         this.stringList = stringList;
    19         this.mContext = context;
    20     }
    21 
    22     @Override
    23     public int getCount() {
    24         return stringList.size();
    25     }
    26 
    27     @Override
    28     public Object getItem(int position) {
    29         return null;
    30     }
    31 
    32     @Override
    33     public long getItemId(int position) {
    34         return 0;
    35     }
    36 
    37     @Override
    38     public View getView(int position, View convertView, ViewGroup parent) {
    39         ViewHolder viewHolder = null;
    40         if (convertView == null) {
    41             convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str,null);
    42             viewHolder = new ViewHolder();
    43             viewHolder.mTextView = (TextView) convertView
    44                     .findViewById(R.id.id_tv_title);
    45             convertView.setTag(viewHolder);
    46         } else {
    47             viewHolder = (ViewHolder) convertView.getTag();
    48         }
    49         viewHolder.mTextView.setText(stringList.get(position));
    50         return convertView;
    51     }
    52 
    53     private final class ViewHolder {
    54         TextView mTextView;
    55     }
    56 }

    item_single_str  Item布局

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical">
     6 
     7     <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     8         android:id="@+id/id_tv_title"
     9         android:layout_width="match_parent"
    10         android:layout_height="50dp"
    11         android:background="#aa111111"
    12         android:gravity="center_vertical"
    13         android:paddingLeft="15dp"
    14         android:text="hello"
    15         android:textColor="#ffffff"
    16         android:textSize="20sp"
    17         android:textStyle="bold"/>
    18 </LinearLayout>

    是不是觉得上面的代码很熟悉,写了很多,甚至都写到吐了,但是如果仔细观察的话,就会发现adapter里面的代码很多都是固定的
    那么我们能不能把那些重复的代码封装起来呢...

    ViewHolder

    先简单的说一下ViewHolder的作用,可以理解为是一个容器,每一个convertView通过setTag来绑定一个ViewHolder对象,convertView中的控件就保存到ViewHolder中,当convertView复用的时候,直接通过getTag从ViewHolder中
    取出来即可,不在需要在到布局文件中通过findViewById去找。通常,Android中写一个列表,无论是ListView还是GridView,我们都是一个activity对应一个adapter,但是如果我们能写出一个通用的adapter的话,是不是会省事很多呢??
    先不说其他的,至少我们的代码会少很多。好,come on ,接着往下。。。

    万能ViewHolder

    从ViewHolder下手,先上代码

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.view.LayoutInflater;
     5 import android.view.View;
     6 
     7 import java.util.HashMap;
     8 import java.util.Map;
     9 
    10 /**
    11  * Created by Administrator on 2017/10/10 0010.
    12  */
    13 
    14 public class ViewHolder {
    15     private View mConvertView;
    16     private Map<Integer,View> viewMap; //保存控件
    17 
    18 
    19 
    20     public ViewHolder(Context context,int layoutId) {
    21         viewMap = new HashMap<Integer,View>();
    22         mConvertView = LayoutInflater.from(context).inflate(layoutId,null);
    23         mConvertView.setTag(this);
    24     }
    25 
    26 
    27     /**
    28      * 获取ViewHolder对象
    29      * @param convertView
    30      * @return
    31      */
    32     public static ViewHolder getViewHolder(View convertView,Context context,int layoutId) {
    33         //先判断convertView组件是否存在,存在的话,说明ViewHolder已经创建
    34         if (convertView == null) {
    35             return new ViewHolder(context,layoutId);
    36         }
    37         return (ViewHolder) convertView.getTag();
    38     }
    39 
    40     /**
    41      * 获取控件
    42      * 由于每一个item布局不一样,控件是未知的,但是,无论是哪一个控件,他的父类都是View
    43      */
    44     public <T extends View > T getView(int viewId){
    45         View view = viewMap.get(viewId); //从Map中去出控件
    46         if(view == null){ //说明,没有有这个控件,到布局中找
    47             view = mConvertView.findViewById(viewId);
    48             viewMap.put(viewId,view);
    49         }
    50         return (T) view;
    51     }
    52 
    53     public View getConvertView(){
    54         return mConvertView;
    55     }
    56 }

    ViewHolder中,我创建了一个Map,用来保存控件,当然你也可以使用其他,比如SparseArray,ArrayMap,相比效率或者性能
    方面,后者会比HashMap好很多,只是我觉得使用HashMap,更多人会更容易理解,如果想使用后两种,那直接替换就可以了,
    他们的区别我在这里就不说了,有兴趣的朋友可以看看这篇文章:http://blog.csdn.net/u010687392/article/details/47809295

    现在再来看看适配器中的代码

    TestAdapter类

     1 .
     2     .省略若干方法
     3     .
     4     
     5      @Override
     6     public View getView(int position, View convertView, ViewGroup parent) {
     7       /* 
     8         常规的写法
     9       ViewHolder viewHolder;
    10         if (convertView == null) {
    11             convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str, null);
    12             viewHolder = new ViewHolder();
    13             viewHolder.mTextView = (TextView) convertView .findViewById(R.id.id_tv_title);
    14             convertView.setTag(viewHolder);
    15         } else {
    16             viewHolder = (ViewHolder) convertView.getTag();
    17         }
    18         viewHolder.mTextView.setText(stringList.get(position));
    19         return convertView;*/
    20 
    21         
    22         //使用万能的ViewHoler协防
    23         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
    24         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
    25         textView.setText(stringList.get(position));
    26         return holder.getConvertView();
    27 
    28 
    29     }
    30 
    31    /* private final class ViewHolder {
    32         TextView mTextView;
    33     }*/

    简单的说一下思路:
    先看TestAdapter中的getView方法,先获取这个ViewHolder对象,而获取ViewHolder 的时候,先判断convertView是否存在,如果存在,则说明ViewHolder对象已经存在,直接通过getTag来获取,如果不存在,则需要将item的布局填充到
    convertView中,再通过setTag进行绑定。其实和常规写法的思路差不多,唯独就是每一次创建新的一个ViewHolder的时候,会创建一个viewMap,这个viewMap是用来保存控件的。然后在通过viewholder对象从viewMap中取出相应的控件
    从而进行赋值。这里需要注意一个就是,getView方法中return 的view,是在ViewHolder填充Item的mConvertView,而不是直接拿getView中的convertView ......哈哈是不是代码量少很多了啊,现在不在需要每次创建一个adapter都要弄一个ViewHolder了
    省事很多,但是......这里只是刚刚开始而已,接着开干....

    万能的adapter适配器:
    前面我们弄了一个通用ViewHolder,但是,我们写出来的列表还是得创建一个adapter,类并没有少,代码量少了一点点,这并不能达到我想要的,我想要的是类也少,代码量也少,万能adapter出来吧...动画片看多了
    在封装一个万成的适配器之前,我们先看一下这个adapter的代码:

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.view.View;
     5 import android.view.ViewGroup;
     6 import android.widget.BaseAdapter;
     7 import android.widget.TextView;
     8 
     9 import java.util.List;
    10 
    11 
    12 /**
    13  * Created by huangjialin on 2017/9/12.
    14  */
    15 public class TestAdapter extends BaseAdapter {
    16     private List<String> stringList;
    17     private Context mContext;
    18 
    19     public TestAdapter(Context context, List<String> stringList) {
    20         this.stringList = stringList;
    21         this.mContext = context;
    22     }
    23 
    24     @Override
    25     public int getCount() {
    26         return stringList.size();
    27     }
    28 
    29     @Override
    30     public Object getItem(int position) {
    31         return stringList.get(position);
    32     }
    33 
    34     @Override
    35     public long getItemId(int position) {
    36         return position;
    37     }
    38 
    39     @Override
    40     public View getView(int position, View convertView, ViewGroup parent) {
    41         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
    42         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
    43         textView.setText(stringList.get(position));
    44         return holder.getConvertView();
    45     }
    46 }

    从代码中,我们可以看到,继承BaseAdapter,实现这4个方法,其中有三个方法写法基本是固定不变的最主要的就是getView方法了,既然我们要弄一个万能适配器,是不是只要把getView方法单独抽出来实现就好,对吧。还有既然是通用的adapter,
    那么数据肯定是不固定的,数据,我们就得需要泛型了....come on ,先创建一个万能适配器的类 PowerfulAdapter

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.widget.BaseAdapter;
     5 import java.util.List;
     6 
     7 /**
     8  * Created by Administrator on 2017/10/11 0011.
     9  * 万能适配器
    10  */
    11 
    12 public abstract class PowerfulAdapter<T> extends BaseAdapter {
    13 
    14     private List<T> stringList;
    15     private Context mContext;
    16 
    17     public PowerfulAdapter(List<T> stringList, Context mContext) {
    18         this.stringList = stringList;
    19         this.mContext = mContext;
    20     }
    21 
    22     @Override
    23     public int getCount() {
    24         return stringList.size();
    25     }
    26 
    27     @Override
    28     public Object getItem(int position) {
    29         return stringList.get(position);
    30     }
    31 
    32     @Override
    33     public long getItemId(int position) {
    34         return position;
    35     }
    36 }

    从代码中我们可以看到,除了这个getView方法我们没有实现之外,另外的三个方法,我们都实现了,并且把这个类弄成抽象的,由于这几个方法,基本写法都是固定的,所以后面我们写适配器的时候,只需要继承这个万成的适配器,并且实现getView一个方法就可以了
    ,另外几个方法我们就不在需要考虑了。为了和前面的TestAdapter区别开来,我们另外创建一个适配器,SecondAdapter这个适配器就继承我们的万能适配器PowerfulAdapter<T>,上代码

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.view.View;
     5 import android.view.ViewGroup;
     6 import android.widget.TextView;
     7 
     8 import java.util.List;
     9 
    10 /**
    11  * Created by Administrator on 2017/10/11 0011.
    12  */
    13 
    14 public class SecondAdapter<T> extends PowerfulAdapter<T>{
    15     private List<String> stringList;
    16     private Context mContext;
    17 
    18     public SecondAdapter(List<T> stringList, Context mContext) {
    19         super(stringList);
    20         this.stringList = (List<String>) stringList;
    21         this.mContext = mContext;
    22     }
    23 
    24     @Override
    25     public View getView(int position, View convertView, ViewGroup viewGroup) {
    26         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
    27         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
    28         textView.setText(stringList.get(position));
    29         return holder.getConvertView();
    30     }
    31 }

    现在我们的适配器,继承这个PowerfulAdapter<T>这个万能适配器,最后面只需要实现一个getView方法就完全可以了,相当省了一般的代码量,有些兄弟就说了,毛线啊,我从头看到尾,我没发现代码少啊,我见你反而多写了几个类呢,
    莫慌,现在我这边只是写了一个列表,,我写了ViewHolder,PowerfulAdapter<T> ,而且这两个类相当于工具类一样,以后所有的listview或者gridview都可以使用,这样效率就会高的多了....实际上封装到这,也差不多可以了,也满足了大部分人的需要了,
    但是,我这么帅,得省点时间出来约会啊,还是觉得代码太多了,接着封装...

    我们观察getView方法会发现

    1 TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
    2 textView.setText(stringList.get(position));

    这些TextView是Android常用的控件,那如果我们能不能先把一些常用的控件先封装起来,在这里就不需要获取了呢,我们试试。。。

    我们在ViewHolder类中加一个这样的方法

     1 /**
     2      * 给TextView设置值
     3      */
     4     public ViewHolder setText(int viewId, String text) {
     5         if (viewId > 0 && text != null) {
     6             TextView tv_Text = getView(viewId);
     7             tv_Text.setText(text);
     8         }
     9         return this;
    10     }

    然后在getView方法中只需要这样写...

     1   @Override
     2     public View getView(int position, View convertView, ViewGroup viewGroup) {
     3         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
     4 
     5         /**
     6          * 旧的写法
     7          */
     8         /*TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
     9         textView.setText(stringList.get(position));*/
    10 
    11         /**
    12          * 在ViewHolder加了setText方法后的写法
    13          */
    14         holder.setText(R.id.id_tv_title, stringList.get(position));
    15 
    16 
    17         return holder.getConvertView();
    18     }

    哈哈哈,是不是代码又比原来少了一点,但是,在ViewHolder中加一些常用控件得自己手动添加,可能刚刚开始会觉得很不全面,当久而久之,加进去多了,就慢慢完善了。
    到这里,基本上一种封装已经完成了。到时候我会将这一部分源码上传,需要的可以下载... 可以在文章后面下载,也可以点击这里 下载

    But,有些人就会说,你前面不是说能够少创建很多Java文件吗,我没有看到在哪里少呢,莫慌,马上来了...

    在回到 PowerfulAdapter<T>这个类,前面我们并没有在这个类中实现getView方法,但是现在我们在该类中实现getView方法,附上代码

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.content.Context;
     4 import android.view.View;
     5 import android.view.ViewGroup;
     6 import android.widget.BaseAdapter;
     7 import java.util.List;
     8 
     9 /**
    10  * Created by Administrator on 2017/10/11 0011.
    11  * 万能适配器
    12  */
    13 
    14 public abstract class PowerfulAdapter<T> extends BaseAdapter {
    15     private Context mContext;
    16     private List<T> stringList;
    17 
    18 
    19     public PowerfulAdapter(Context mContext, List<T> stringList) {
    20         this.mContext = mContext;
    21         this.stringList = stringList;
    22     }
    23 
    24     @Override
    25     public int getCount() {
    26         return stringList.size();
    27     }
    28 
    29     @Override
    30     public Object getItem(int position) {
    31         return stringList.get(position);
    32     }
    33 
    34     @Override
    35     public long getItemId(int position) {
    36         return position;
    37     }
    38 
    39     @Override
    40     public View getView(int position, View convertView, ViewGroup viewGroup) {
    41         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
    42         convert(holder,stringList.get(position));
    43         return holder.getConvertView();
    44     }
    45 
    46     /**
    47      * 对外提供一个抽象方法
    48      */
    49     public abstract void convert(ViewHolder helper, T item);
    50 
    51 }

    在MainActivity的直接一个匿名内部类来实现convert方法,附上代码

     1 package com.example.administrator.listviewtest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.view.View;
     6 import android.widget.AdapterView;
     7 import android.widget.Button;
     8 import android.widget.ListView;
     9 import android.widget.Toast;
    10 
    11 import java.util.ArrayList;
    12 import java.util.List;
    13 
    14 public class MainActivity extends Activity {
    15     private ListView listview;
    16     private List<String> stringList;
    17 
    18     private PowerfulAdapter adapter;
    19 
    20 
    21     @Override
    22     protected void onCreate(Bundle savedInstanceState) {
    23         super.onCreate(savedInstanceState);
    24         setContentView(R.layout.activity_main);
    25 
    26         stringList = new ArrayList<String>();
    27         for (int i = 0; i < 50; i++) {
    28             stringList.add("不写adapter,直接匿名内部类实现" + (i + 50));
    29         }
    30 
    31         listview = (ListView) findViewById(R.id.listview);
    32 
    33 
    34         adapter = new PowerfulAdapter<String>(this, stringList, R.layout.item_single_str) {
    35             @Override
    36             public void convert(ViewHolder holder, String item) {
    37                 holder.setText(R.id.id_tv_title, item);
    38                 Button button = holder.getView(R.id.button);
    39 
    40                 button.setOnClickListener(new View.OnClickListener() {
    41                     @Override
    42                     public void onClick(View view) {
    43                         Toast.makeText(MainActivity.this, "我是按钮,我被点击了----->", Toast.LENGTH_LONG).show();
    44                     }
    45                 });
    46 
    47             }
    48         };
    49         listview.setAdapter(adapter);
    50 
    51         listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    52             @Override
    53             public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
    54                 Toast.makeText(MainActivity.this, "我被点击了----->" + i, Toast.LENGTH_LONG).show();
    55             }
    56         });
    57 
    58     }
    59 
    60 
    61 }

    最后附上一张运行的效果图

    ok,是不是连adapter都不需要创建了,如果项目有几十,几百个列表,就相当于少了几十几百个Java类,多爽啊,哈哈最后附上两种封装方式的源码链接,
    有需要的朋友可以下载,同时,如果有朋友发现bug或者将已解决问题的方法,或者有更好的封装方式,欢迎留言,一块探讨学习。

    源码链接

    通用适配器封装源码:https://github.com/343661629/---ListView-GridView

    另一种封装方式源码: https://github.com/343661629/ListView-

  • 相关阅读:
    OCM_第十四天课程:Section6 —》数据库性能调优_各类索引 /调优工具使用/SQL 优化建议
    OCM_第十三天课程:Section6 —》数据库性能调优 _结果缓存 /多列数据信息采集统计/采集数据信息保持游标有效
    OCM_第十二天课程:Section6 —》数据库性能调优_ 资源管理器/执行计划
    使用NuGet时的一个乌龙
    .net调用存储过程碰到的一个问题
    数据库的备份与还原
    创建link server链接服务器碰到的问题及解决办法
    如何管理好项目的DLL
    项目中Enum枚举的使用
    .NET开发知识体系
  • 原文地址:https://www.cnblogs.com/huangjialin/p/7661328.html
Copyright © 2011-2022 走看看