Android之自定义实现BaseAdapter(通用适配器四)
最近又学习到了一种自定义通用适配器的方法,刚好可以结合前面的一起来写一下,接下来先看代码吧。
CommonAdapter.java
public abstract class CommonAdapter<T, V extends CommonAdapter.ViewHolder> extends BaseAdapter {
//上下文
private Context mContext;
//集合数据
private List<T> mDatas;
//布局ID
@LayoutRes
private int mLayoutId;
//解析布局LayoutInflater
private LayoutInflater inflater;
public CommonAdapter(Context context,@LayoutRes int layoutId){
this(context,new ArrayList<T>(), layoutId);
}
public CommonAdapter(Context context,List<T> datas,@LayoutRes int layoutId){
mContext = context;
mLayoutId = layoutId;
mDatas = datas;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mDatas == null ? 0 : 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) {
//这里和我们平时的判断是一样的
if (convertView == null) {
//解析对应的布局
convertView = inflater.inflate(mLayoutId,null);
/**
* 这里是我们这里要重点理解的,来看看这里表示什么意思呢?
* 首先看我们上面有个泛型V extends CommonAdapter.ViewHolder
* 其实这里主要的目的就是拿到泛型V的Class类型,通过该类型,
* 我们就可以创建子类的实例了,getClass().getGenericSuperclass()表示获取
* 带有泛型的父类,相当于拿到了我们这里带有参数的CommonAdapter了,然后强转
* 成ParameterizedType类型,通过getActualTypeArguments()拿到我们需要的泛型
* 参数位置,0代表T泛型参数,1表示V泛型参数,所以这里通过getActualTypeArguments()[1]
* 拿到了我们的泛型V。最后转换成我们的Class类型,就拿到了CommonAdapter.ViewHolder子类的类型了
*/
Class clazz = (Class)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
Log.d("IT_Real", "getView: className" + clazz.getSimpleName());
try {
//通过反射拿到子类带有一个参数的构造方法
Constructor constructor = clazz.getConstructor(View.class);
//开始创建子类实例,并将convertView作为参数传过去,这样convertView就保存到对应类中了
ViewHolder viewHolder = (ViewHolder) constructor.newInstance(convertView);
//设置标志为ViewHolder类对象
convertView.setTag(viewHolder);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
//获取具体的子类实例
V holder = (V) convertView.getTag();
//子类重写该方法,可以通过这两个参数拿到对应的参数了。
onBind(holder,mDatas.get(position));
//返回convertView;
return convertView;
}
/**
* 把所有添加数据的方法都聚集到这个类中,避免刷新数据时
* 出现两种所指地址不相同的情况。外界不需要自己定义数据集了
* 直接调用这些方法来存数据,和刷新数据
* @param data
*/
public void add(T data){
mDatas.add(data);
notifyDataSetChanged();
}
public void add(int index, T data){
mDatas.add(index,data);
notifyDataSetChanged();
}
public void addAll(Collection<? extends T> collection){
mDatas.addAll(collection);
notifyDataSetChanged();
}
public void remove(T data){
mDatas.remove(data);
notifyDataSetChanged();
}
public void remove(int index){
mDatas.remove(index);
notifyDataSetChanged();
}
public void clear(){
mDatas.clear();
notifyDataSetChanged();
}
/**
* 子类实现该方法
* @param holder ViewHolder的具体子类对象
* @param data 要设置的数据
*/
public abstract void onBind(V holder,T data);
public static class ViewHolder{
private View mItemView;
//用于保存所有布局中的控件
private SparseArray<View> mViews;
public ViewHolder(View itemView){
mItemView = itemView;
mViews = new SparseArray<>();
}
/**
* 为子类提供该方法,只需要之类传对应的
* 控件id我们即可为他返回一个控件对象、
* 同时通过SparseArray<View>省去了我们每次复用
* 相同控件时,去重复findViewById,提高了程序的效率
* @param viewId
* @param <T>
* @return
*/
protected <T extends View> T getView(int viewId){
View view = mViews.get(viewId);
if(view == null){
view = mItemView.findViewById(viewId);
mViews.put(viewId,view);
}
return (T)view;
}
}
}
TextAdapter .java
public class TextAdapter extends CommonAdapter<String,TextAdapter.MyViewHolder> {
public TextAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
}
/**
* 通过该方法即可实现对应的数据设置
* @param holder ViewHolder的具体子类对象
* @param data 要设置的数据
*/
@Override
public void onBind(MyViewHolder holder, String data) {
holder.textView.setText(data);
}
/**
* CommonAdapter.ViewHolder的子类
*/
public static class MyViewHolder extends CommonAdapter.ViewHolder{
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
//通过getView直接拿到对应的数据
textView = getView(android.R.id.text1);
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ListView mList;
private TextAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = (ListView)findViewById(R.id.mList);
mAdapter = new TextAdapter(this);
mAdapter.add("数据1");
mAdapter.add("数据2");
mAdapter.add("数据3");
mAdapter.add("数据4");
mAdapter.add("数据5");
mAdapter.add("数据6");
mList.setAdapter(mAdapter);
}
}
效果图如下:
通过上面的代码,是不是觉得这种封装方法也值得一用呢?其实不管采用哪种方式,看个人的习惯吧,不过最近觉得有一点我们要养成习惯,就是将添加数据集的方法封装到通用适配器中,这样会避免以后在加载网络数据时,造成数据不显示的情况,包括刷新也不需要我们手动调用了,每次只要更新数据,对应的方法会自动刷新适配器。下面就示范一下刷新数据的情况
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ListView mList;
private TextAdapter mAdapter;
private Button mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = (ListView)findViewById(R.id.mList);
mAdapter = new TextAdapter(this);
mAdapter.add("数据1");
mAdapter.add("数据2");
mAdapter.add("数据3");
mAdapter.add("数据4");
mAdapter.add("数据5");
mAdapter.add("数据6");
mList.setAdapter(mAdapter);
mBtn = (Button)findViewById(R.id.mBtn);
}
public void onClick(View view){
switch (view.getId()) {
case R.id.mBtn:
List<String> newDatas = new ArrayList<>();
for(int i = 0; i < 10; i ++){
newDatas.add("新数据"+ i);
}
mAdapter.addAll(newDatas);
break;
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/mBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="点击刷新"/>
<ListView
android:id="@+id/mList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/mBtn"
/>
</RelativeLayout>
运行结果如下:
上面我们在布局中添加了一个点击刷新按钮,当点击的时候就会刷新数据,我们只需要调用一下mAdapter.addAll()方法即可,这样并不需要我们自己手动调用刷新数据了。也不会造成我们的数据错乱,保证了数据的唯一性。