简述
先前在一微信讨论组里讨论起adapter和viewholder的抽取,有的说他们项目里用的是抽得他妈都不认识...他妈都不认识那还怎么用? 也看了github上几个开源出来的抽取方法,有的抽取过度,有的不便使用,还要自己记id什么的,感觉都不是很满意,于是回头看看自己项目里封装的,感觉还是比较合理的,兼顾了可读性和重用性,封装也适度,于是把它放出来,与大家交流交流.
抽取封装BaseAdapter的基本原理(以listview为例)
convertview:
listview本身提供的复用机制,缓存的是item的rootview,避免了每次getview都去将整个xml解析成一个view对象.
内部是若干个View数组,数组个数等于getItemTypeCount(),每次getview方法的参数里传入的convertview对象就是根据getItemType(pisition)在指定的数组中取缓存的view.
viewholder
缓存了itemview内部需要用到的各个子view对象,避免了每次getview时都从rootview对象中findviewById,减少了xml解析的时间.
viewholder的复用是通过与converview绑定,借用convertview的复用机制来达到复用的效果.绑定与取出是通过view.settag(obj)和view.gettag()来实现的.
封装技巧
viewholder
不仅封装了各子view对象,也同时封装rootview对象,便于设定整个item的点击事件,长按事件,这样就避开了对listview设置onItemClickListener时可能发生的无焦点等坑爹事件.
提供设置布局的xml的方法,让子类实现,用@LayoutRes注解限定返回值.
使用Butterkinfe结合其对应的android studio插件来快速生成view对象的代码.
将数据和事件设置到各view上封装成统一的方法,让adapter里直接调用holder的方法,而无多余代码. 数据ben采用泛型,让子类实现时直接指定.
代码:
public abstract class SuperLvHolder {
public View rootView;
public SuperLvHolder(Activity context){
rootView = View.inflate(context,setLayoutRes(),null);
ButterKnife.bind(this,rootView);
}
protected abstract @LayoutRes int setLayoutRes();
/**
* 一般情况下,实现这个方法就足够了
* @param context
* @param bean
*/
public abstract void assingDatasAndEvents(Activity context, T bean);
/**
* 如果有需要,才实现这个方法
* @param context activity实例,用于一些点击事件
* @param bean 该条目的数据
* @param position 该条目所在的位置
* @param isLast 是否为最后一条,有些情况下需要用到
* @param isListViewFling listview是不是在惯性滑动,备用.一般图片加载框架会提供全局暂停和恢复的方法,无需此参数
* @param datas 整个listview对应的数据
* @param superAdapter adapter对象引用,可用于触发notifydatesetChanged()方法刷新整个listview,比如更改的单选按钮
*/
public void assingDatasAndEvents(Activity context, T bean, int position ,boolean isLast,
boolean isListViewFling,List datas, SuperLvAdapter superAdapter){
assingDatasAndEvents(context,bean);
}
}
adapter
继承BaseAdapter,四个抽象方法都用重写,getview里方法使用上面封装好的viewholder,只需要提供一个抽象方法generateNewHolder(int itemtype).而且,即使是多种type的item,也无需更改getview里的逻辑.
adapter 对外提供添加数据,刷新数据,删除数据等功能,这些功能抽取成接口并让adapter实现。
adapter不指定数据类型,因为多种类型时,可能会有多种数据类型。
/**
* 单一的item
* Created by Administrator on 2016/4/15 0015.
*/
public abstract class SuperLvAdapter extends BaseAdapter implements Refreshable {
List datas;
Activity context;
boolean isListViewFling;
public boolean isListViewFling() {
return isListViewFling;
}
public void setListViewFling(boolean listViewFling) {
isListViewFling = listViewFling;
}
public SuperLvAdapter(@NonNull List datas, Activity context){
if (datas == null){
throw new RuntimeException("datas cannot be null");
}
this.datas = datas;
this.context = context;
}
@Override
public int getCount() {
if (datas == null )
return 0;
return datas.size();
}
@Override
public Object getItem(int position) {
if (datas == null)
return null;
return datas.get(position);
}
@Override
public long getItemId(int position) {
if (datas == null){
return 0;
}
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SuperLvHolder holder = null;
if (convertView == null){
holder = generateNewHolder(context,getItemViewType(position));//考虑多种类型的item
convertView = holder.rootView;
convertView.setTag(holder);
}else {
holder = (SuperLvHolder) convertView.getTag();
}
holder.assingDatasAndEvents(context,datas.get(position),position,position == getCount() -1,isListViewFling,datas,this);
return convertView;
}
protected abstract SuperLvHolder generateNewHolder(Activity context, int itemViewType);//子类需要实现的唯一方法
@Override
public void refresh(List newData){
if (newData == null){
datas.clear();
notifyDataSetChanged();
return;
}
if (datas == null){
datas = newData;
notifyDataSetChanged();
}else {
datas.clear();
datas.addAll(newData);
notifyDataSetChanged();
}
}
@Override
public void addAll(List newData){
if (newData == null){
return;
}
if (datas == null){
datas = newData;
notifyDataSetChanged();
}else {
datas.addAll(newData);
notifyDataSetChanged();
}
}
@Override
public void clear(){
if (datas != null){
datas.clear();
notifyDataSetChanged();
}
}
@Override
public void delete(int position){
if (datas != null && position < getCount()){
datas.remove(position);
notifyDataSetChanged();
}
}
public List getListData(){
return datas;
}
@Override
public void add(Object object) {
if (object ==null)
return;
try {
datas.add(object);
notifyDataSetChanged();
}catch (Exception e){
}
}
}
RecycleView的adapter封装形式类似
有一点不同是ViewHolder必须继承RecyView.ViewHolder,构造函数被限定只能传一个itemview对象进来,所以layout文件id不能封装到holder内部,只能从外部指定并inflate成view后传递进来.
public abstract class SuperRcvHolder extends RecyclerView.ViewHolder {
public View rootView;//用于外部对itemview的引用操作
public SuperRcvHolder(View itemView) {
super(itemView);
rootView = itemView;
ButterKnife.bind(this,rootView);
}
....
}
使用
使用技巧:
listview,recycleview的数据与界面达到完全的一一对应,如果服务器返回的数据不对应,那么重新组合,如果有一个item无需数据,那么在datas里插入null或无意义的数据,holder多一种类型来处理即可.
SuperLvAdapter:
单一类型item时,使用匿名实现类即可,多种类型时,重写getItemViewTypeCount和getItemViewType(position) 即可,无需更改getview内部的逻辑.
SuperLvHolder:
写子类的时候在子类内部指定layout文件,一般情况下,实现assingDatasAndEvents(Activity context, String bean)就可以,如果要用到int position ,boolean isLast,就实现更多参数的同名方法,此时,上面那个简化的方法空实现即可.
如果该holder在多个地方使用,那么可以作为单独的类,达到复用的目的.
SuperRcvAdapter和SuperRcvHolder
adapter一般情况下都使用匿名子类.多个item时分别指定类型和对应的hoder即可.
holder一般也使用匿名子类.如果在其他页面需要复用,那么可以写成单独的子类.其layout文件需要在构造函数前传入,已封装好方法.
示例代码
AbstractListview 的 SuperLvAdapter:
adapter:
ListView listView = new ListView(this);
ArrayList datas = new ArrayList<>();
SuperLvAdapter adapter = new SuperLvAdapter(datas, this) {
@Override
protected SuperLvHolder generateNewHolder(Activity context,int viewType) {
return new CustomHolder(context);
}
};
listView.setAdapter(adapter);
adapter.add("hhhh");
viewholder的实现:
class CustomHolder extends SuperLvHolder {
@Bind(R.id.tv_text)
TextView mTvText;
public CustomHolder(Activity context) {
super(context);
}
@Override
protected int setLayoutRes() {
return R.layout.holder_demo_list;
}
@Override
public void assingDatasAndEvents(Activity context, String bean) {
mTvText.setText(bean);
}
}
RecycleView 的 SuperRcvAdapter:
SuperRcvAdapter,多种类型下的使用
mAdapter = new SuperRcvAdapter(datas, mActivity) {
public static final int TYPE_0 = 0;
public static final int TYPE_1 = 1;
@Override
protected SuperRcvHolder generateCoustomViewHolder(int viewType) {
switch (viewType) {
case TYPE_0:
return new CustomHolder(inflate(R.layout.holder_demo_list));
case TYPE_1:
return new CustomHolder2(inflate(R.layout.holder_demo_list_2));
default:
return new SuperRcvHolder(inflate(R.layout.holder_demo_list_2)) {//匿名子类
private TextView tv_text;
@Override
public void assignDatasAndEvents(Activity context, String data) {
super.assignDatasAndEvents(context, data);
tv_text.setText(data);
}
};
}
}
@Override
public int getItemViewType(int position) {
if (position % 2 == 0) {//偶数位
return TYPE_0;
} else {//奇数位
return TYPE_1;
}
}
};
mRecyclerView.setAdapter(mAdapter);
holder的实现:
class CustomHolder extends SuperRcvHolder {
@Bind(R.id.tv_text)
TextView mTvText;
public CustomHolder(View itemView) {
super(itemView);
}
@Override
public void assignDatasAndEvents(Activity context, String data) {
mTvText.setText(data);
}
//备用
@Override
public void assignDatasAndEvents(Activity context, String data, int position, boolean isLast,
boolean isListViewFling, List datas, SuperRcvAdapter superRecyAdapter) {
super.assignDatasAndEvents(context, data, position, isLast, isListViewFling, datas, superRecyAdapter);
}
}
代码地址
SuperAdapter : https://github.com/glassLake/SuperAdapter