本文内容参考自慕课网。
ListView、GridView 是我们在 Android 中很常用的控件,通常来说,对于每一个控件都要写对应的 ViewHolder 和 Adapter。但是当项目中的 ListView 很多时,写 ViewHolder 就成了一件很枯燥的事。那么有没有办法,写一个通用的 ViewHolder 与 Adapter,将可复用的代码放在里面,对于具体的控件,稍作修改就能适用呢?
传统的 Adapter 写法
首先是布局:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.commonviewholder.MainActivity">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</RelativeLayout>
主布局里简单地放了一个 ListView。
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
android:id="@+id/item_listview_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="THIS IS TITLE"
android:textColor="#444"
android:textSize="30sp" />
<TextView
android:id="@+id/item_listview_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="This is content"
android:textSize="25sp" />
<TextView
android:id="@+id/item_listview_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:padding="10dp"
android:text="2016-9-17"
android:textColor="#898989"
android:textSize="16sp" />
</LinearLayout>
接着要添加 Bean 类,来获取 item 里的各控件:
package com.example.commonviewholder.bean;
/**
* Created by 某宅 on 2016/9/17.
*/
public class Bean {
String itemTitle;
String itemContent;
String itemTime;
public Bean(String itemTitle, String itemContent, String itemTime) {
this.itemTitle = itemTitle;
this.itemContent = itemContent;
this.itemTime = itemTime;
}
public String getItemTitle() {
return itemTitle;
}
public String getItemContent() {
return itemContent;
}
public String getItemTime() {
return itemTime;
}
}
自定义 Adapter, 这里用继承自 BaseAdapter 为例:
package com.example.commonviewholder.adapter;
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 com.example.commonviewholder.R;
import com.example.commonviewholder.bean.Bean;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public class Adapter extends BaseAdapter {
private LayoutInflater mInflater;
private List<Bean> mDatas;
public Adapter(Context context, List<Bean> datas) {
mInflater = LayoutInflater.from(context);
mDatas = datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public Object getItem(int i) {
return mDatas.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
if (view == null) {
view = mInflater.inflate(R.layout.item_listview, viewGroup, false);
viewHolder = new ViewHolder();
viewHolder.mTitle = (TextView) view.findViewById(R.id.item_listview_title);
viewHolder.mContent = (TextView) view.findViewById(R.id.item_listview_content);
viewHolder.mTime = (TextView) view.findViewById(R.id.item_listview_time);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
Bean bean = mDatas.get(i);
viewHolder.mTitle.setText(bean.getItemTitle());
viewHolder.mContent.setText(bean.getItemContent());
viewHolder.mTime.setText(bean.getItemTime());
return view;
}
private class ViewHolder {
TextView mTitle;
TextView mContent;
TextView mTime;
}
}
最后在 MainActivity 中定义数据集,获取并绑定 Adapter:
package com.example.commonviewholder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import com.example.commonviewholder.adapter.Adapter;
import com.example.commonviewholder.bean.Bean;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView mListView;
private List<Bean> mDatas;
private Bean mBean;
private Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
}
/**
* 初始化 View
*/
private void initView() {
mListView = (ListView) findViewById(R.id.listview);
mListView.setAdapter(mAdapter);
}
/**
* 初始化数据
*/
private void initData() {
mDatas = new ArrayList<>();
//假装自己有很多数据
mBean = new Bean("TITLE1", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE2", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE3", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE4", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE5", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE6", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE7", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE8", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE9", "CONTENT", "2016-9-18");
mDatas.add(mBean);
mBean = new Bean("TITLE10", "CONTENT", "2016-9-18");
mDatas.add(mBean);
//获得自定义 Adapter 的实例
mAdapter = new Adapter(this, mDatas);
}
}
这样一个简单的 ListView 示例就完成了。
通用 ViewHolder 的编写
既然是通用的 ViewHolder,首先肯定是要将这个类抽出来。
package com.example.commonviewholder.adapter;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by 某宅 on 2016/9/17.
*/
public class ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private int mPosition;
private ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
mViews = new SparseArray<>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
mConvertView.setTag(this);
mPosition = position;
}
/**
* 获取 ViewHolder 的实例
* 如果 convertView 存在,直接从 convertView 获取
* 否则 new 一个
*
* @param context
* @param convertView
* @param parent
* @param layoutId 布局id
* @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);
} else {
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.mPosition = position;//防止 item 误复用
return (ViewHolder) convertView.getTag();
}
}
/**
* 通过 ViewId 获取控件实例
*
* @param viewId
* @param <T>
* @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;
}
public View getConvertView() {
return mConvertView;
}
}
接着 Adapter 就可以简化为:
package com.example.commonviewholder.adapter;
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 com.example.commonviewholder.R;
import com.example.commonviewholder.bean.Bean;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public class Adapter extends BaseAdapter {
private LayoutInflater mInflater;
private List<Bean> mDatas;
private Context mContext;
public Adapter(Context context, List<Bean> datas) {
mInflater = LayoutInflater.from(context);
mDatas = datas;
mContext = context;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public Object getItem(int i) {
return mDatas.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = ViewHolder.get(mContext, view, viewGroup, R.layout.item_listview, i);
Bean bean = mDatas.get(i);
TextView itemTitle = viewHolder.getView(R.id.item_listview_title);
TextView itemContent = viewHolder.getView(R.id.item_listview_content);
TextView itemTime = viewHolder.getView(R.id.item_listview_time);
itemTitle.setText(bean.getItemTitle());
itemContent.setText(bean.getItemContent());
itemTime.setText(bean.getItemTime());
return viewHolder.getConvertView();
}
}
省略了之前繁琐的获取 convertView 与 ViewHolder 的方法,并且如果 ListView 越多,布局越复杂,这种写法的简便性就会越明显。
通用的 Adapter
既然 ViewHolder 的重复代码可以加以封装,那么能不能对 Adapter 做同样的事呢?答案当然是肯定的!新建一个 CommonAdapter:
package com.example.commonviewholder.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
protected List<T> mDatas;
protected Context mContext;
protected LayoutInflater inflater;
public CommonAdapter(Context context, List<T> datas){
mContext = context;
mDatas = datas;
inflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public Object getItem(int i) {
return mDatas.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public abstract View getView(int i, View view, ViewGroup viewGroup);
}
这样我们的 Adapter 就可以继承自 CommonAdapter, 将重复的代码全部清除掉:
package com.example.commonviewholder.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.commonviewholder.R;
import com.example.commonviewholder.bean.Bean;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public class Adapter extends CommonAdapter<Bean> {
public Adapter(Context context, List<Bean> datas) {
super(context, datas);
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = ViewHolder.get(mContext, view, viewGroup, R.layout.item_listview, i);
Bean bean = mDatas.get(i);
TextView itemTitle = viewHolder.getView(R.id.item_listview_title);
TextView itemContent = viewHolder.getView(R.id.item_listview_content);
TextView itemTime = viewHolder.getView(R.id.item_listview_time);
itemTitle.setText(bean.getItemTitle());
itemContent.setText(bean.getItemContent());
itemTime.setText(bean.getItemTime());
return viewHolder.getConvertView();
}
}
上面的代码足够简单了吗?不,因为在 getView() 里我们还是要重复写几句代码。因此可以对 CommonAdapter 进一步简化,不用抽象 getView() 方法,而是将重复的代码放在 getView() 里,抽象一个接受必要参数的方法来提供给子类获取控件实例:
package com.example.commonviewholder.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.example.commonviewholder.R;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
protected List<T> mDatas;
protected Context mContext;
protected LayoutInflater inflater;
private int layoutId;
public CommonAdapter(Context context, List<T> datas, int layoutId) {
mContext = context;
mDatas = datas;
inflater = LayoutInflater.from(mContext);
this.layoutId = layoutId;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public T getItem(int i) {
return mDatas.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = ViewHolder.get(mContext, view, viewGroup, layoutId, i);
convert(viewHolder, getItem(i));
return viewHolder.getConvertView();
}
public abstract void convert(ViewHolder viewHolder, T t);
}
因为修改了 getView() 里获取 ViewHolder 的语句,因此布局 id 改由构造方法获取。
package com.example.commonviewholder.adapter;
import android.content.Context;
import android.widget.TextView;
import com.example.commonviewholder.R;
import com.example.commonviewholder.bean.Bean;
import java.util.List;
/**
* Created by 某宅 on 2016/9/17.
*/
public class Adapter extends CommonAdapter<Bean> {
public Adapter(Context context, List<Bean> datas) {
super(context, datas, R.layout.item_listview);
}
@Override
public void convert(ViewHolder viewHolder, Bean bean) {
TextView itemTitle = viewHolder.getView(R.id.item_listview_title);
TextView itemContent = viewHolder.getView(R.id.item_listview_content);
TextView itemTime = viewHolder.getView(R.id.item_listview_time);
itemTitle.setText(bean.getItemTitle());
itemContent.setText(bean.getItemContent());
itemTime.setText(bean.getItemTime());
}
}
进一步的优化
考虑到我们常用的控件有 TextView、ImageView 等等,我们可以提前将这些控件的通用方法抽取出来。这里以 TextView 为例,我们常用的无非是为其设置输出语句,因此可以在 ViewHolder 类里提前写好相应的方法:
/**
* 子控件的通用方法
*
* @param viewId
* @param text
* @return
*/
public ViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
这样 Adapter 里的方法可以得到进一步的简化:
@Override
public void convert(ViewHolder viewHolder, Bean bean) {
viewHolder.setText(R.id.item_listview_title, bean.getItemTitle())
.setText(R.id.item_listview_content, bean.getItemContent())
.setText(R.id.item_listview_time, bean.getItemTime());
}
最后附上自己贴在 GitHUb 上的源码:https://github.com/Zhai-Wang/Practices/tree/master/commonviewholder