在android日常开发中,我们用到最多的就是加载数据,尤其是适配器,Adapter是连接后端数据和前端显示的适配器接口。如下图直观的表达了Data、Adapter、View三者的关系:
1、我刚刚接触android的时候的做法常常是使用Android自带的simpleAdapter,simpleAdapter的扩展性好,以listview为例子。常规的做法会在主布局写一个listview,然后你写一个item布局,可以定义各种各样的布局出来,可以放上ImageView,还可以放上Button等等。那时候,我认为满足了我日常开发的所有需求,嘿嘿
item.xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textSize="20sp"
/>
</LinearLayout>
Activity中的代码:
public class SimpleAdapterActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//map.put(键,值)
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < 3; i++) {
map.put("title", "苹果"+i);
map.put("img", R.drawable.icon);
list.add(map);
}
SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.simple, new String[] { "title", "img" }, new int[] { R.id.title, R.id.img });
//参数依次是Context context, List<? extends Map<String, ?>> //data,int resource, String[] from, int[] to
你的listview.setAdapter(adapter);
}
}
总结:这种方法,我使用了很久,那时候啥也不懂,总觉得自定义adapter真的是好遥远的事情。在学校里比赛的时候,遇到一个问题,要在item.xml布局中加一个Ratingbar,类似做一个点评的操作,当时就抓狂了,各种百度自定义适配器,总是显示不出来Ratingbar中的数值。。。心酸呐,所以我打算对自定义adapter做一点点尝试。。。
2、继承baseAdapter,打造自定义适配器
adapter中的方法全透析:
package adapter;
import java.util.List;
import java.util.Map;
import com.example.adaptertest.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class TestAdapter extends BaseAdapter {
private List<String> mData;
protected Context mContext;
private int layoutId;
// 带参数的构造方法
public TestAdapter(Context context, List<String> data, int layoutId) {
this.mData = data; // 传入的是item集合的数据
this.mContext = context; // 传入的是当前的activity.this(上下文)
this.layoutId = layoutId; // 传入的是item子布局
}
// 获取当前子项的总数
@Override
public int getCount() {
return mData.size(); // 返回传入数据List的总数
}
// 获取选中位置的item数值
@Override
public Object getItem(int position) {
return mData.get(position); // 当前位置(position)数据
}
// 获取选中位置
@Override
public long getItemId(int position) {
return position;
}
// 获取当前子项并逐一进行显示。当执行操作的时候通过position进行对当前项的操作
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 动态加载item的布局
convertView = LayoutInflater.from(mContext).inflate(layoutId, null);
TextView tv = (TextView) convertView.findViewById(R.id.checkBtn);
String itemData = mData.get(position);// 获取当前项的数值
tv.setText(itemData);// 赋值
//tv.setOnClickListener(l) 当然如果子view中想监听一些方法也写在这里
return convertView; // 返回的是加载数据后的view
}
}
总结:当然你会了这些后你就可以自定义自己的适配器了。但是有木有遇到过当数据很多很多的时候,往下滑ListView时有时候会卡顿,这就需要我们来优化它了。而且往后的日子里,如果你的listview不幸有imageview的话,而且你也没做缓存还会oom。。。一路走过来都是坑有木有
3、convertView重用,ViewHolder的子View复用(常规的写法)
adapter中的代码:
package adapter;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter
{
private Context mContext;
private List<String> mDatas;
private int layoutId;
public MyAdapter(Context context, List<String> mDatas,int layoutId)
{
this.layoutId= layoutId;
this.mContext = context;
this.mDatas = mDatas;
}
@Override
public int getCount()
{
return mDatas.size();
}
@Override
public Object getItem(int position)
{
return mDatas.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder viewHolder = null;
//这样做的好处是仅仅第一次getview的时候会动态加载item的布局convertView,而且不用只有第一次才需要findviewbyId了,因为getview的方法是每个item都会执行,所以我们把item中的所有控件放到容器viewHolder里面了,然后再把它放到convertview的tag里面
if (convertView == null)
{
convertView = LayoutInflater.from(mContext).inflate(layoutId, null);
viewHolder = new ViewHolder();
viewHolder.mTextView = (TextView) convertView
.findViewById(R.id.tv_title);
convertView.setTag(viewHolder);
} else
{
viewHolder = (ViewHolder) convertView.getTag();
}
//重用子view控件对象
viewHolder.mTextView.setText(mDatas.get(position));
return convertView;
}
//相当于容器
private final class ViewHolder
{
TextView mTextView;
}
}
总结:ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView()的方法,这个方法会传入一个convertView的参数,这个方法返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存空间,即创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环)的构件,就是当ListView的Item从滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的缓存Item的View,所以说如果能重用这个convertView,就会大大改善性能。
我们都知道在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用ViewHolder,把每一个item的子View控件对象都放在Holder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
以上两段话说到我心坎里了,就借鉴过来了。。。嘿嘿
4、使用鸿老师的万能适配器。。。
首先分析下ViewHolder的作用,通过convertView.setTag与convertView进行绑定,然后当convertView复用时,直接从与之对于的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的时间~
也就是说,实际上们每个convertView会绑定一个ViewHolder对象,这个viewHolder主要用于帮convertView存储布局中的控件。
那么我们只要写出一个通用的ViewHolder,然后对于任意的convertView,提供一个对象让其setTag即可;
既然是通用,那么我们这个ViewHolder就不可能含有各种控件的成员变量了,因为每个Item的布局是不同的,最好的方式是什么呢?
提供一个容器,专门存每个Item布局中的所有控件,而且还要能够查找出来;既然需要查找,那么ListView肯定是不行了,需要一个键值对进行保存,键为控件的Id,值为控件的引用,相信大家立刻就能想到Map;但是我们不用Map,因为有更好的替代类,就是我们android提供的SparseArray这个类,和Map类似,但是比Map效率,不过键只能为Integer.
package adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
public class ViewHolder {
private SparseArray<View> store;
private int mPosition;
private View mContentView;
private Context mContext;
public ViewHolder(Context context, int layoutId, int position,
ViewGroup parent) {
store = new SparseArray<View>();
mContext = context;
mPosition = position;
mContentView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
mContentView.setTag(this);
}
public static ViewHolder get(Context context, int layoutId,
View convertView, int position, ViewGroup parent) {
if (convertView == null)
return new ViewHolder(context, layoutId, position, parent);
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.mPosition = position;
return viewHolder;
}
/**
* 获取
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(int id) {
View view = store.get(id);
if (null == view) {
view = mContentView.findViewById(id);
store.put(id, view);
}
return (T) view;
}
public View getContentView() {
return mContentView;
}
public Context getContext() {
return mContext;
}
public ViewHolder setImage(int id, Bitmap bm) {
ImageView iv = getView(id);
iv.setImageBitmap(bm);
return this;
}
public ViewHolder setImage(int id, int resId) {
ImageView iv = getView(id);
iv.setImageDrawable(mContext.getResources().getDrawable(resId));
return this;
}
// // 传递URL
// public ViewHolder setImage(int id, String url) {
// ImageView iv = getView(id);
// // BitmapDisplayConfig a = new BitmapDisplayConfig();
// // // 设置图片的大小
// // a.setBitmapMaxSize(new BitmapSize(150, 150));
// // 设置未加载时候的图片
// BitmapUtils bitmapUtils = new BitmapUtils(mContext);
// bitmapUtils.configDefaultLoadingImage(R.drawable.empty);
// bitmapUtils.display(iv, url);
// // new BitmapUtils(mContext).display(iv, url);
// return this;
// }
public ViewHolder setText(int id, String text) {
TextView tv = getView(id);
tv.setText(text);
return this;
}
// public ViewHolder setTextWithdrable(int id, int drawableid, String text)
// {
// TextView tv = getView(id);
// Drawable drawable = mContext.getResources().getDrawable(drawableid);
// tv.setCompoundDrawables(drawable, null, null, null);
// drawable.setBounds(0, 0, drawable.getMinimumWidth(),
// drawable.getMinimumHeight());// 必须设置图片大小,否则不显示
// tv.setText(text);
// return this;
// }
// boolean值
public ViewHolder setBoolean(int id, Boolean bool) {
CheckBox chek = getView(id);
chek.setChecked(bool);
return this;
}
public ViewHolder setTextFromHtml(int id, String text) {
TextView tv = getView(id);
tv.setText(Html.fromHtml(text));
return this;
}
public ViewHolder setTextFromHtmlWithimg(int id, String text,
String strImage, ImageGetter ig) {
TextView tv = getView(id);
tv.setText(Html.fromHtml(strImage, ig, null));
tv.append(Html.fromHtml(text));
return this;
}
public int getPosition() {
return mPosition;
}
}
打造通用的Adapter:
Adapter一般需要保持一个List对象,存储一个Bean的集合,不同的ListView,Bean肯定是不同的,这个CommonAdapter肯定需要支持泛型,内部维持一个List,就解决我们的问题了;
package adapter;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public abstract class AbListAdapter<T> extends BaseAdapter {
private List<T> mData;
protected Context mContext;
private int layoutId;
public AbListAdapter(Context context, List<T> data, int layoutId) {
this.mData = data;
this.mContext = context;
this.layoutId = layoutId;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public T getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = ViewHolder.get(mContext, layoutId, convertView,
position, parent);
converView(viewHolder, mData.get(position));
return viewHolder.getContentView();
}
protected abstract void converView(ViewHolder viewHolder, T bean);
public List<T> getData() {
return mData;
}
}
现在就在activity中你只要这么写了。。。。
mAdapter = new CommonAdapter<String>(getApplicationContext(),
R.layout.item_single_str, mDatas)
{
@Override
protected void convert(ViewHolder viewHolder, String item)
{
viewHolder.setText(R.id.id_tv_title, item);
}
};
convertView里面只要一行代码了~~~
声明:最后一步通用适配器是鸿洋大神写的博客:http://blog.csdn.net/lmj623565791/article/details/38902805
总结:个人觉得日常开发中懂得了如何自定义adapter后,第三种方法后,如果碰到要设置imageview的时候,可以采用volley框架或者xutils里面的BitmapUtils,里面自带了缓存机制,就不会在快速滑动过程中报oom了。。。。好了,不说了,我要继续跳坑了