前言
接近半年的时间没有写博客了,今年公司的项目有点多,比较忙,没时间写,这是其一。其次是,这半年来,有时间的时候,
我都会看看自己以前写的博客,也许是以前刚刚写博客,经验不足,感觉写出来的博客质量很不好,而最近,也经常在网上看别人的博客
学到到了很多,趁最近项目都差不多收尾了,就写写今年下半年的第一篇博客...
从事Android开发工作有几年时间了,我发现做手机应用的话,基本都离不开ListView,GridView,这些控件,尤其是ListView
我曾开发过一个项目,70%的都是列表,光写adapter,就接近上百个,写到最后,都感觉已经麻木了...比如下面这个例子
MainActivity页面
package com.example.huangjialin.listviewadapter; import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView; import java.util.ArrayList;
import java.util.List; public class MainActivity extends Activity {
private ListView listview;
private List<String> stringList;
private TestAdapter adapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); stringList = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
stringList.add("测试" + i);
} listview = (ListView) findViewById(R.id.listview);
adapter = new TestAdapter(this, stringList);
listview.setAdapter(adapter);
}
}
主页面布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
> <ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
TestAdapter页面
package com.example.huangjialin.listviewadapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List; /**
* Created by huangjialin on 2017/9/12.
*/
public class TestAdapter extends BaseAdapter {
private List<String> stringList;
private Context mContext; public TestAdapter(Context context, List<String> stringList) {
this.stringList = stringList;
this.mContext = context;
} @Override
public int getCount() {
return stringList.size();
} @Override
public Object getItem(int position) {
return null;
} @Override
public long getItemId(int position) {
return 0;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str,null);
viewHolder = new ViewHolder();
viewHolder.mTextView = (TextView) convertView
.findViewById(R.id.id_tv_title);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.mTextView.setText(stringList.get(position));
return convertView;
} private final class ViewHolder {
TextView mTextView;
}
}
item_single_str Item布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/id_tv_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#aa111111"
android:gravity="center_vertical"
android:paddingLeft="15dp"
android:text="hello"
android:textColor="#ffffff"
android:textSize="20sp"
android:textStyle="bold"/>
</LinearLayout>
是不是觉得上面的代码很熟悉,写了很多,甚至都写到吐了,但是如果仔细观察的话,就会发现adapter里面的代码很多都是固定的
那么我们能不能把那些重复的代码封装起来呢...
ViewHolder
先简单的说一下ViewHolder的作用,可以理解为是一个容器,每一个convertView通过setTag来绑定一个ViewHolder对象,convertView中的控件就保存到ViewHolder中,当convertView复用的时候,直接通过getTag从ViewHolder中
取出来即可,不在需要在到布局文件中通过findViewById去找。通常,Android中写一个列表,无论是ListView还是GridView,我们都是一个activity对应一个adapter,但是如果我们能写出一个通用的adapter的话,是不是会省事很多呢??
先不说其他的,至少我们的代码会少很多。好,come on ,接着往下。。。
万能ViewHolder
从ViewHolder下手,先上代码
package com.example.administrator.listviewtest; import android.content.Context;
import android.view.LayoutInflater;
import android.view.View; import java.util.HashMap;
import java.util.Map; /**
* Created by Administrator on 2017/10/10 0010.
*/ public class ViewHolder {
private View mConvertView;
private Map<Integer,View> viewMap; //保存控件 public ViewHolder(Context context,int layoutId) {
viewMap = new HashMap<Integer,View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId,null);
mConvertView.setTag(this);
} /**
* 获取ViewHolder对象
* @param convertView
* @return
*/
public static ViewHolder getViewHolder(View convertView,Context context,int layoutId) {
//先判断convertView组件是否存在,存在的话,说明ViewHolder已经创建
if (convertView == null) {
return new ViewHolder(context,layoutId);
}
return (ViewHolder) convertView.getTag();
} /**
* 获取控件
* 由于每一个item布局不一样,控件是未知的,但是,无论是哪一个控件,他的父类都是View
*/
public <T extends View > T getView(int viewId){
View view = viewMap.get(viewId); //从Map中去出控件
if(view == null){ //说明,没有有这个控件,到布局中找
view = mConvertView.findViewById(viewId);
viewMap.put(viewId,view);
}
return (T) view;
} public View getConvertView(){
return mConvertView;
}
}
ViewHolder中,我创建了一个Map,用来保存控件,当然你也可以使用其他,比如SparseArray,ArrayMap,相比效率或者性能
方面,后者会比HashMap好很多,只是我觉得使用HashMap,更多人会更容易理解,如果想使用后两种,那直接替换就可以了,
他们的区别我在这里就不说了,有兴趣的朋友可以看看这篇文章:http://blog.csdn.net/u010687392/article/details/47809295
现在再来看看适配器中的代码
TestAdapter类
.
.省略若干方法
. @Override
public View getView(int position, View convertView, ViewGroup parent) {
/*
常规的写法
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str, null);
viewHolder = new ViewHolder();
viewHolder.mTextView = (TextView) convertView .findViewById(R.id.id_tv_title);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.mTextView.setText(stringList.get(position));
return convertView;*/ //使用万能的ViewHoler协防
ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
textView.setText(stringList.get(position));
return holder.getConvertView(); } /* private final class ViewHolder {
TextView mTextView;
}*/
简单的说一下思路:
先看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的代码:
package com.example.administrator.listviewtest; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView; import java.util.List; /**
* Created by huangjialin on 2017/9/12.
*/
public class TestAdapter extends BaseAdapter {
private List<String> stringList;
private Context mContext; public TestAdapter(Context context, List<String> stringList) {
this.stringList = stringList;
this.mContext = context;
} @Override
public int getCount() {
return stringList.size();
} @Override
public Object getItem(int position) {
return stringList.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
textView.setText(stringList.get(position));
return holder.getConvertView();
}
}
从代码中,我们可以看到,继承BaseAdapter,实现这4个方法,其中有三个方法写法基本是固定不变的最主要的就是getView方法了,既然我们要弄一个万能适配器,是不是只要把getView方法单独抽出来实现就好,对吧。还有既然是通用的adapter,
那么数据肯定是不固定的,数据,我们就得需要泛型了....come on ,先创建一个万能适配器的类 PowerfulAdapter
package com.example.administrator.listviewtest; import android.content.Context;
import android.widget.BaseAdapter;
import java.util.List; /**
* Created by Administrator on 2017/10/11 0011.
* 万能适配器
*/ public abstract class PowerfulAdapter<T> extends BaseAdapter { private List<T> stringList;
private Context mContext; public PowerfulAdapter(List<T> stringList, Context mContext) {
this.stringList = stringList;
this.mContext = mContext;
} @Override
public int getCount() {
return stringList.size();
} @Override
public Object getItem(int position) {
return stringList.get(position);
} @Override
public long getItemId(int position) {
return position;
}
}
从代码中我们可以看到,除了这个getView方法我们没有实现之外,另外的三个方法,我们都实现了,并且把这个类弄成抽象的,由于这几个方法,基本写法都是固定的,所以后面我们写适配器的时候,只需要继承这个万成的适配器,并且实现getView一个方法就可以了
,另外几个方法我们就不在需要考虑了。为了和前面的TestAdapter区别开来,我们另外创建一个适配器,SecondAdapter这个适配器就继承我们的万能适配器PowerfulAdapter<T>,上代码
package com.example.administrator.listviewtest; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; import java.util.List; /**
* Created by Administrator on 2017/10/11 0011.
*/ public class SecondAdapter<T> extends PowerfulAdapter<T>{
private List<String> stringList;
private Context mContext; public SecondAdapter(List<T> stringList, Context mContext) {
super(stringList);
this.stringList = (List<String>) stringList;
this.mContext = mContext;
} @Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
textView.setText(stringList.get(position));
return holder.getConvertView();
}
}
现在我们的适配器,继承这个PowerfulAdapter<T>这个万能适配器,最后面只需要实现一个getView方法就完全可以了,相当省了一般的代码量,有些兄弟就说了,毛线啊,我从头看到尾,我没发现代码少啊,我见你反而多写了几个类呢,
莫慌,现在我这边只是写了一个列表,,我写了ViewHolder,PowerfulAdapter<T> ,而且这两个类相当于工具类一样,以后所有的listview或者gridview都可以使用,这样效率就会高的多了....实际上封装到这,也差不多可以了,也满足了大部分人的需要了,
但是,我这么帅,得省点时间出来约会啊,还是觉得代码太多了,接着封装...
我们观察getView方法会发现
TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
textView.setText(stringList.get(position));
这些TextView是Android常用的控件,那如果我们能不能先把一些常用的控件先封装起来,在这里就不需要获取了呢,我们试试。。。
我们在ViewHolder类中加一个这样的方法
/**
* 给TextView设置值
*/
public ViewHolder setText(int viewId, String text) {
if (viewId > 0 && text != null) {
TextView tv_Text = getView(viewId);
tv_Text.setText(text);
}
return this;
}
然后在getView方法中只需要这样写...
@Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象 /**
* 旧的写法
*/
/*TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
textView.setText(stringList.get(position));*/ /**
* 在ViewHolder加了setText方法后的写法
*/
holder.setText(R.id.id_tv_title, stringList.get(position)); return holder.getConvertView();
}
哈哈哈,是不是代码又比原来少了一点,但是,在ViewHolder中加一些常用控件得自己手动添加,可能刚刚开始会觉得很不全面,当久而久之,加进去多了,就慢慢完善了。
到这里,基本上一种封装已经完成了。到时候我会将这一部分源码上传,需要的可以下载... 可以在文章后面下载,也可以点击这里 下载
But,有些人就会说,你前面不是说能够少创建很多Java文件吗,我没有看到在哪里少呢,莫慌,马上来了...
在回到 PowerfulAdapter<T>这个类,前面我们并没有在这个类中实现getView方法,但是现在我们在该类中实现getView方法,附上代码
package com.example.administrator.listviewtest; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List; /**
* Created by Administrator on 2017/10/11 0011.
* 万能适配器
*/ public abstract class PowerfulAdapter<T> extends BaseAdapter {
private Context mContext;
private List<T> stringList; public PowerfulAdapter(Context mContext, List<T> stringList) {
this.mContext = mContext;
this.stringList = stringList;
} @Override
public int getCount() {
return stringList.size();
} @Override
public Object getItem(int position) {
return stringList.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
convert(holder,stringList.get(position));
return holder.getConvertView();
} /**
* 对外提供一个抽象方法
*/
public abstract void convert(ViewHolder helper, T item); }
在MainActivity的直接一个匿名内部类来实现convert方法,附上代码
package com.example.administrator.listviewtest; import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast; import java.util.ArrayList;
import java.util.List; public class MainActivity extends Activity {
private ListView listview;
private List<String> stringList; private PowerfulAdapter adapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); stringList = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
stringList.add("不写adapter,直接匿名内部类实现" + (i + 50));
} listview = (ListView) findViewById(R.id.listview); adapter = new PowerfulAdapter<String>(this, stringList, R.layout.item_single_str) {
@Override
public void convert(ViewHolder holder, String item) {
holder.setText(R.id.id_tv_title, item);
Button button = holder.getView(R.id.button); button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "我是按钮,我被点击了----->", Toast.LENGTH_LONG).show();
}
}); }
};
listview.setAdapter(adapter); listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(MainActivity.this, "我被点击了----->" + i, Toast.LENGTH_LONG).show();
}
}); } }
最后附上一张运行的效果图
ok,是不是连adapter都不需要创建了,如果项目有几十,几百个列表,就相当于少了几十几百个Java类,多爽啊,哈哈最后附上两种封装方式的源码链接,
有需要的朋友可以下载,同时,如果有朋友发现bug或者将已解决问题的方法,或者有更好的封装方式,欢迎留言,一块探讨学习。
源码链接
通用适配器封装源码:https://github.com/343661629/---ListView-GridView
另一种封装方式源码: https://github.com/343661629/ListView-