对于ListView相信大家都很熟悉,在这个RecycleView盛行的年代现在再谈ListView似乎有点Out,
但是作为Android老牌控件,在一些要求不那么高的情况下,
ListView还是有可取之道的(好歹实现比RecycleView简单点)。
传统的优化复用convertView,ViewHolder保存控件索引等等,大家都很熟悉了,
这里就不多说,再进一步超级好用的万能适配器相信很多人也很熟悉了,
对于不熟悉的童鞋,
请移驾鸿洋大神的博客
Android 快速开发系列 打造万能的ListView GridView 适配器.
先贴上大神代码:
public class ViewHolder
{
private final SparseArray<View> mViews;
private int mPosition;
private View mConvertView;
private ViewHolder(Context context, ViewGroup parent, int layoutId,
int position)
{
this.mPosition = position;
this.mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
// setTag
mConvertView.setTag(this);
}
/**
* 拿到一个ViewHolder对象
*
* @param context
* @param convertView
* @param parent
* @param layoutId
* @param position
* @return
*/
public static ViewHolder get(Context context, View convertView,
ViewGroup parent, int layoutId, int position)
{
if (convertView == null)
{
return new ViewHolder(context, parent, layoutId, position);
}
return (ViewHolder) convertView.getTag();
}
public View getConvertView()
{
return mConvertView;
}
/**
* 通过控件的Id获取对于的控件,如果没有则加入views
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId)
{
View view = mViews.get(viewId);
if (view == null)
{
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 为TextView设置字符串
*
* @param viewId
* @param text
* @return
*/
public ViewHolder setText(int viewId, String text)
{
TextView view = getView(viewId);
view.setText(text);
return this;
}
/**
* 为ImageView设置图片
*
* @param viewId
* @param drawableId
* @return
*/
public ViewHolder setImageResource(int viewId, int drawableId)
{
ImageView view = getView(viewId);
view.setImageResource(drawableId);
return this;
}
/**
* 为ImageView设置图片
*
* @param viewId
* @param drawableId
* @return
*/
public ViewHolder setImageBitmap(int viewId, Bitmap bm)
{
ImageView view = getView(viewId);
view.setImageBitmap(bm);
return this;
}
/**
* 为ImageView设置图片
*
* @param viewId
* @param drawableId
* @return
*/
public ViewHolder setImageByUrl(int viewId, String url)
{
ImageLoader.getInstance(3, Type.LIFO).loadImage(url,
(ImageView) getView(viewId));
return this;
}
public int getPosition()
{
return mPosition;
}
}
public abstract class CommonAdapter<T> extends BaseAdapter
{
protected LayoutInflater mInflater;
protected Context mContext;
protected List<T> mDatas;
protected final int mItemLayoutId;
public CommonAdapter(Context context, List<T> mDatas, int itemLayoutId)
{
this.mContext = context;
this.mInflater = LayoutInflater.from(mContext);
this.mDatas = mDatas;
this.mItemLayoutId = itemLayoutId;
}
@Override
public int getCount()
{
return mDatas.size();
}
@Override
public T 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)
{
final ViewHolder viewHolder = getViewHolder(position, convertView,
parent);
convert(viewHolder, getItem(position));
return viewHolder.getConvertView();
}
public abstract void convert(ViewHolder helper, T item);
private ViewHolder getViewHolder(int position, View convertView,
ViewGroup parent)
{
return ViewHolder.get(mContext, convertView, parent, mItemLayoutId,
position);
}
}
但是这个这个万能适配器对于实现多重行布局还是有点欠缺的,网上也少有这种文章,今天我就自己小小改进了这个适配器以支持实现复杂行布局。
首先,多重行布局实现我们需要重写BaseAdapter的方法:
//返回多重行布局的样式数量
getViewTypeCount()
//返回行布局类型,取值是0~样式数量-1
getItemViewType(int position)
之前万能适配器构造方法:
/**
* 实现单独行布局构造方法
*
* @param context 上下文
* @param datas 数据源
* @param itemlayout 布局ID
*/
public CommonAdapter(Context context, List<T> datas, int itemlayout) {
this.mContext = context;
this.mDatas = datas;
this.mItemlayoutID = itemlayout;
}
只传入一个布局itemlayout
而我们需要实现多重样式那就传入样式的数组嘛
多写一个构造方法
/**
* 实现复杂行布局构造方法
*
* @param context 上下文
* @param datas 数据源
* @param layouts 复杂行布局ID数组
*/
public CommonAdapter(Context context, List<T> datas, int[] layouts) {
this.mContext = context;
this.mlayoutIDs = layouts;
this.mDatas = datas;
//决定是否是多重行布局
this.isMoreType = true;
}
之后对于重写的两个方法我们就需要这样来实现:
/**
* 返回多重布局数量
*
* @return
*/
@Override
public int getViewTypeCount() {
return isMoreType ? mlayoutIDs.length :
super.getViewTypeCount();
}
/**
* 返回多重布局类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return isMoreType ? decodeID(decide(getItem(position)))
: super.getItemViewType(position);
}
//我们需要封装一个方法,由子类来重写此方法根据传入Item数据决定返回哪一个布局 ,不定为abstract方法只因为此方法不一定要实现
/**
* 根据数据判断返回的布局
*
* @param t
* @return layoutID
*/
public int decide(T t) {
return 0;
}
而又由于getItemViewType(int position)返回的数值需要在0到getViewTypeCount这个方法返回数值之间:所以,我们需要一个方法来转化下布局ID的数值:
/**
* 由于官方规定,getItemViewType返回数值需在0~mDatas.size()之间,所以需要将布局ID转化下
*
* @param layoutid
* @return
*/
private int decodeID(int layoutid) {
for (int i = 0; i < mlayoutIDs.length; i++) {
if (layoutid == mlayoutIDs[i]) {
return i;
}
}
return 0;
}
那么,现在的getView()方法就需要这样写了:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//由isMoreType判断是否是多重行布局
ViewHolder holder = isMoreType ?
//如果是我们就需要转换getItemViewType方法回传的数值转化为LayoutID
ViewHolder.get(mContext, convertView, parent, encodeID(getItemViewType(position)), position)
: ViewHolder.get(mContext, convertView, parent, mItemlayoutID, position);
convert(holder, getItem(position));
return holder.getConvertView();
}
private int encodeID(int index) {
return mlayoutIDs[index];
}
那么,现在完整的CommonAdapter代码就是这样:
package com.puti.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List;
/**
* 创建于 by puti on 2017/2/24.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
//数据集合
protected List<T> mDatas;
//复杂行布局所有布局ID
private int[] mlayoutIDs;
//单独行布局ID
private int mItemlayoutID;
//上下文
protected Context mContext;
//是否是复杂行布局
private boolean isMoreType = false;
/**
* 实现单独行布局构造方法
*
* @param context 上下文
* @param datas 数据源
* @param itemlayout 布局ID
*/
public CommonAdapter(Context context, List<T> datas, int itemlayout) {
this.mContext = context;
this.mDatas = datas;
this.mItemlayoutID = itemlayout;
}
/**
* 实现复杂行布局构造方法
*
* @param context 上下文
* @param datas 数据源
* @param layouts 复杂行布局ID数组
*/
public CommonAdapter(Context context, List<T> datas, int[] layouts) {
this.mContext = context;
this.mlayoutIDs = layouts;
this.mDatas = datas;
this.isMoreType = true;
}
/**
* 返回多重布局数量
*
* @return
*/
@Override
public int getViewTypeCount() {
return isMoreType ? mlayoutIDs.length :
super.getViewTypeCount();
}
/**
* 返回多重布局类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return isMoreType ? decodeID(decide(getItem(position)))
: super.getItemViewType(position);
}
/**
* 根据数据判断返回的布局
*
* @param t
* @return layoutID
*/
public int decide(T t) {
return 0;
}
;
/**
* 由于官方规定,getItemViewType返回数值需在0~mDatas.size()之间,所以需要将布局ID转化下
*
* @param layoutid
* @return
*/
private int decodeID(int layoutid) {
for (int i = 0; i < mlayoutIDs.length; i++) {
if (layoutid == mlayoutIDs[i]) {
return i;
}
}
return 0;
}
private int encodeID(int index) {
return mlayoutIDs[index];
}
@Override
public int getCount() {
return mDatas == null ? 0 : mDatas.size();
}
@Override
public T 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 holder = isMoreType ?
ViewHolder.get(mContext, convertView, parent, encodeID(getItemViewType(position)), position)
: ViewHolder.get(mContext, convertView, parent, mItemlayoutID, position);
convert(holder, getItem(position));
return holder.getConvertView();
}
/**
* 用来设置布局
*
* @param holder 保存了Item的控件
* @param t 行数据
*/
public abstract void convert(ViewHolder holder, T t);
}
接下来,我们来实验下:
package com.puti;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import com.puti.adapter.CommonAdapter;
import com.puti.adapter.ViewHolder;
import com.puti.model.Bean;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Bean> data;
private ListView mListView;
private int[] layoutIds;
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
init();
}
/**
* 控件与数据绑定
*/
private void init() {
myAdapter = new MyAdapter(this, data, layoutIds);
mListView.setAdapter(myAdapter);
}
/**
* 初始化控件
*/
private void initView() {
mListView = (ListView) findViewById(R.id.listview);
}
/**
* 创建数据源
*/
private void initData() {
layoutIds = new int[]{R.layout.layout1, R.layout.layout2, R.layout.layout3};
data = new ArrayList<>();
for (int i = 0; i < 30; i++) {
switch (i % 3) {
case 0:
data.add(new Bean("类型为1的标题" + i, "#ff0000", "类型1"));
break;
case 1:
data.add(new Bean("类型为2的标题" + i, "#00ff00", "类型2"));
break;
case 2:
data.add(new Bean("类型为3的标题" + i, "#0000ff", "类型3"));
break;
}
}
}
class MyAdapter extends CommonAdapter<Bean> {
public MyAdapter(Context context, List<Bean> datas, int[] layouts) {
super(context, datas, layouts);
}
@Override
public void convert(ViewHolder holder, Bean bean) {
switch (getItemViewType(holder.getPosition())) {
case 0:
holder.setText(R.id.textView, bean.getTitle())
.setBackgroundColor(Color.parseColor(bean.getColor()));
break;
case 1:
holder.setText(R.id.textView, bean.getTitle())
.setBackgroundColor(Color.parseColor(bean.getColor()));
break;
case 2:
holder.setText(R.id.textView, bean.getTitle())
.setBackgroundColor(Color.parseColor(bean.getColor()));
break;
}
}
//重写此方法
@Override
public int decide(Bean bean) {
if (bean.getType().equals("类型1")) {
return R.layout.layout1;
} else if (bean.getType().equals("类型2")) {
return R.layout.layout2;
} else if (bean.getType().equals("类型3")) {
return R.layout.layout3;
}
return 0;
}
}
}
简单写了一个数据Bean类:
package com.puti.model;
/**
* 创建于 by puti on 2017/2/24.
*/
public class Bean {
//标题
private String title;
//颜色
private String color;
//类型
private String type;
public Bean(String title, String color, String type) {
this.title = title;
this.color = color;
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
再之后就是布局:
//主界面就放了一个ListView,当然GridView也是行的
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.puti.MainActivity">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
再而是三个类型的布局:
layout1:
<?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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_weight="1"
android:gravity="start"
android:text="TextView" />
</LinearLayout>
layout2:
<?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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="68dp"
android:layout_weight="1"
android:gravity="center"
android:text="TextView" />
</LinearLayout>
layout3:
<?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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="58dp"
android:layout_weight="1"
android:gravity="end"
android:text="TextView" />
</LinearLayout>
只为了简单演示效果
这是效果图:
第一次写博客,写的不好不完善之处请见谅。以后慢慢改善