package com.scott.uidemo.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.Image;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* author: scott
* Created at scott on 2016/8/9.
*/
public abstract class ListViewBaseAdapter<T> extends BaseAdapter {
protected List<T> mDatas;
protected Context mContext;
protected LayoutInflater mInflater;
private final String TAG = "ListViewBaseAdapter";
public ListViewBaseAdapter(List<T> datas, Context context) {
mDatas = datas;
mContext = context;
mInflater = LayoutInflater.from(context);
}
@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 int getViewTypeCount() {
return super.getViewTypeCount();
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder bVh;
//由于parent为AbsListView实例,而如果我们inflate itemView,那们itemview的
//layoutparams为GroupParams,这样会丢失itemview根布局的margin属性,所以
//在外部添加一个Linearlayout相当于加载后itemview的layoutparams为marginparams
LinearLayout ll = new LinearLayout(mContext);
ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if (convertView == null) {
convertView = mInflater.inflate(onInflateItemView(getItemViewType(position)), ll, true);
bVh = onCreateViewHolder(convertView);
//保存当前itemview对应的type
bVh.viewType = getItemViewType(position);
convertView.setTag(bVh);
} else {
bVh = (BaseViewHolder) convertView.getTag();
//当当前可复用的convertview的viewtype类型和当前需要的的view的viewtype类型
//不相等时,那么无法复用该view,需要重新加载一个
if (bVh.viewType != getItemViewType(position)) {
convertView = mInflater.inflate(onInflateItemView(getItemViewType(position)), ll, true);
bVh = onCreateViewHolder(convertView);
bVh.viewType = getItemViewType(position);
convertView.setTag(bVh);
}
}
onBindViewHolder(bVh, position);
return convertView;
}
/***
* 当初始化ViewHolder会调用该方法,我们需要返回一个继承自BaseViewHolder
* 的类的对象,该对象应该包含了itemView中我们需要的控件的实例,并且这些
* 实例已经被初始化
*
* @param itemView
* @return
*/
protected abstract BaseViewHolder onCreateViewHolder(View itemView);
/***
* 当我们需要为ViewHolder中的空件设置值的时候会调用该方法,该方法中我们需要根据position为
* ViewHolder中的控件设置值
*
* @param vh
* @param position
*/
protected abstract void onBindViewHolder(BaseViewHolder vh, int position);
/**
* 当我们需要加载item的布局文件时会调用该方法,我们需要返回一个布局的资源ID
*
* @return
*/
protected abstract int onInflateItemView(int viewType);
/***
* 所有的子类都该有一个内部的ViewHolder类继承自该类
* 继承自该类的viewholder只需将其中的view变量和对应的resId用
* BindView注解绑定即可
*
* @BindView TextView tv;
* 这样在onCreateViewHolder时只需new一个ViewHolder并返回即可
*/
abstract class BaseViewHolder {
private int viewType;
View itemView;
protected BaseViewHolder(View v) {
itemView = v;
bindView();
}
/***
* 把viewholder中的view变量绑定到对应的view
*/
private void bindView() {
//this 是runntime变量,只有在运行时才能确定
Class cls = this.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
BindView bv = f.getAnnotation(BindView.class);
if (bv == null) {
continue;
}
f.setAccessible(true);
View v = itemView.findViewById(bv.value());
try {
f.set(this, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/***
* 将当前的数据和控件绑定,但是传入的数据的类必须在类上面标识
* BindData注解,元素值可以为空
* 而类中的字段必须标识@BindData(resId)注解
* 相当于将字段值和控件绑定
* @param data
*/
protected void bindData(Object data) {
Class cls = data.getClass();
BindData bd = (BindData) cls.getAnnotation(BindData.class);
//当前传入的data没有标识BindData注解,则无法进行数据绑定
if (bd == null) {
return;
}
Field[] fields = cls.getFields();
for (Field f : fields) {
BindData bData = f.getAnnotation(BindData.class);
//当前字段没有标识BindData注解则无法绑定数据
if (bData == null) {
continue;
}
Class vCls = getClass();
Field[] vFields = vCls.getDeclaredFields();
for (Field vf : vFields) {
//如果当前ViewHolder中的控件字段没有绑定资源id,
//相当于没有初始化则无法绑定数据
BindView bv = vf.getAnnotation(BindView.class);
if (bv == null) {
continue;
}
f.setAccessible(true);
//绑定数据
setValue(vf, f, data, bv, bData);
}
}
}
/***
* 绑定当前数据
* @param vf viewholder 中的字段
* @param f data 中的字段
* @param data 数据
* @param bv viewholder 中view字段的BindView注解对象
* @param bData Data 中的 值字段的BindData 注解对象
*/
private void setValue(Field vf, Field f, Object data
, BindView bv, BindData bData) {
//当字段的resId 和 控件的resId 相同则进行数据绑定
//当前只支持TextView 绑定String 数据
//ImageView 绑定 Bitmap 数据
if (bv.value() == bData.value()) {
if (vf.getType() == TextView.class) {
try {
Object o = vf.get(this);
TextView tv = (TextView) o;
tv.setText((String) f.get(data));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (vf.getType() == ImageView.class) {
try {
Object o = vf.get(this);
ImageView iv = (ImageView) o;
iv.setImageBitmap((Bitmap) f.get(data));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/***
* 用于标识将资源id和控件进行绑定,元素不可为空
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface BindView {
int value();
}
/***
* 用于标识将数据Moudle 和 指定的资源id对应的控件进行绑定,
* 元素可为空,默认为 -1, 当标识moudle类时可以为空,但标识
* 字段时不能为空,否则无法绑定
*/
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindData {
int value() default -1;
}
}
利用注解反射以及baseAdapter封装了一个自动将ViewHolder中的控件和资源Id绑定,并且自动将moudle中的数据和ViewHolder中数据绑定的Adapter.
用法:
moudle
package com.scott.uidemo.moudle;
import android.graphics.Bitmap;
import com.scott.uidemo.R;
import com.scott.uidemo.adapter.ListViewBaseAdapter;
/**
* author: scott
* Created at scott on 2016/8/9.
*/
@ListViewBaseAdapter.BindData
public class TestMoudle {
@ListViewBaseAdapter.BindData(R.id.tv_content)
public String data;
@ListViewBaseAdapter.BindData(R.id.iv_content)
public Bitmap bmp;
}
adapter:
package com.scott.uidemo.adapter;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.scott.uidemo.R;
import com.scott.uidemo.moudle.TestMoudle;
import java.util.List;
/**
* author: scott
* Created at scott on 2016/8/9.
*/
public class MyTestAdapter extends ListViewBaseAdapter<TestMoudle>{
public MyTestAdapter(List<TestMoudle> datas, Context context) {
super(datas, context);
}
@Override
protected BaseViewHolder onCreateViewHolder(View itemView) {
MyHolder holder = new MyHolder(itemView);
return holder;
}
@Override
protected void onBindViewHolder(BaseViewHolder vh, int position) {
TestMoudle moudle = mDatas.get(position);
vh.bindData(moudle);
}
@Override
protected int onInflateItemView(int viewType) {
return R.layout.list_test_item;
}
final class MyHolder extends BaseViewHolder {
protected MyHolder(View v) {
super(v);
}
@BindView(R.id.tv_content)
TextView tvName;
@BindView(R.id.iv_content)
ImageView ivName;
}
}
xml文件
<?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="wrap_content" 这里的layout_width 和layout_height 以及其他的layout_xx可以直接设置,加载到listview中可以正常显示
android:orientation="vertical">
<TextView
android:id="@+id/tv_content"
android:text="测试文字"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:src="@mipmap/ic_launcher"
android:id="@+id/iv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>