之前写过一篇使用RecyclerView,一句代码就够了,介绍了一个功能较完善的RecyclerView框架的实现。该框架虽然代码不多,但是仍然不够简洁,耦合度也比较高,难以扩展。现将里面的核心部分 OneAdapter 抽取出来,去掉不必要的泛型、类型判断和其他方法,以实现最简单、通用性和扩展性最好的Adapter。
在Github上搜索adatper,选Java语言,有5K+的记录,主要也都是RecyclerView或ListView的适配器封装。既然已经有这么多实现在先,这里再实现一遍有意义吗?
有的,这里的实现是最简单、代码最少的。
OneAdapter代码如下:
/**
* A custom adapter, supports multi-ItemViewType
*
* Created by rome753 on 2018/2/1.
*/
public class OneAdapter extends RecyclerView.Adapter {
private final List
OneAdatper继承自RecyclerView.Adapter,重写了4个方法,并在其中增加了两个List,核心代码只有几行,在 getItemViewType(int position) 这个方法中。
原因
很多同学开发时看到有列表就来一个RecyclerView,然后又实现一个Adapter。这两步都是没有必要的。
先说第一步,RecyclerView并不是有列表就使用的。Recycle的意思是回收,也就是说,只有在需要回收时才使用。什么时候需要回收呢?列表数据项很多或者单个数据项占内存很大时。其他情况下,比如类似微信的设置页面那种简单的列表,不需要回收,用ScrollView实现就可以了,代码更简单,性能更好。这应该也是Google让开发者从ListView迁移到RecyclerView的目的。
再说第二步,每个RecyclerView实现一个Adapter也是冗余的。Adapter的本质是控制列表中每一项的视图(View)与数据(Data)的对应关系,所以它应该只做一件事:RecyclerView把某一项视图传过来时,Adapter把数据传给视图。然而现在Adapter中处理了数据类型和视图类型,这导致它跟具体业务耦合度很高,尤其是数据类型和视图类型多样时。
举个例子:
class MyAdapter extends BaseAdapter> {
private final int TYPE_HEADER = 0;
private final int TYPE_COINS = 1;
@Override
public int getItemCount() {
return mData.size();
}
@Override
public BaseHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return new BaseHolder(new CoinNumberView(parent.getContext()));
} else {
return new BaseHolder(new BuyCoinsView(parent.getContext(), BuyCoinActivity.this));
}
}
@Override
public void onBindViewHolder(BaseHolder holder, int position) {
if (holder.itemView instanceof BuyCoinsView) {
BuyCoinsView buyCoinsView = (BuyCoinsView) holder.bindView;
buyCoinsView.bindDataByPosition(mData.get(position), position);
} else if (holder.itemView instanceof CoinNumberView) {
CoinNumberView coinNumberView = (CoinNumberView) holder.bindView;
coinNumberView.bindData(null);
}
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
} else {
return TYPE_COINS;
}
}
}
这里为了给列表增加一个Header,在Adapter中增加一个类型,然后不得不用 if...else... 或者 switch 语句判断ItemViewType的类型、ViewHolder的类型和ItemView的类型。
Adapter依赖所有类型的所有对象,画图来看是这样的:
这里只是两种类型,如果有4,5种乃至7,8种,那么Adapter就爆炸了!
原理
OneAdapter解决了Adapter的过度耦合问题,它只依赖OneListener和OneViewHolder这两个类,只关联List
无论有多少种数据类型,都只需要在外部实现OneListener和OneViewHolder,给OneAdapter传入OneListener列表即可。
OneAdapter不依赖具体的数据类型,使用Object表示数据类型,而不是泛型。这样做是因为泛型一般针对一种或固定几种不确定的类型,而Adapter中不但有多种不确定的类型、而且具体有几种也是不固定的,因此无法使用泛型。为了传入数据不限制于Object类型,在OneAdapter中的 setData(List> data) 方法参数使用了泛型的不确定类型。
OneListener代码如下:
/**
* A listener for: define item view type and create ViewHolder, outside of the adapter
*/
public interface OneListener{
/**
* Is the position or the data suits for this OneListener?
* @param position the data's position int the list
* @param o the data
* @return true/false
*/
boolean isMyItemViewType(int position, Object o);
/**
* Create a ViewHolder for this OneListener
* @param parent RecyclerView
* @return OneViewHolder
*/
OneViewHolder getMyViewHolder(ViewGroup parent);
}
OneListener是一个接口,它建立了列表中具体位置、具体数据与具体OneViewHolder的对应关系。实际上每个OneListener实例表示列表中一种条目类型。它里面有两个方法。
- isMyItemViewType(int position, Object o) 方法让实现者根据位置或者该位置的数据判断是不是当前OneListener对应的条目类型。
- getMyViewHolder(ViewGroup parent) 方法让实现者实现当前OneListener对应的OneViewHolder子类。
OneListener也不依赖具体的数据类型,因为判断条目类型并不一定是根据数据类型判断,也可能根据位置判断。这给了调用者最大的灵活度。虽然OneViewHolder有泛型,但是OneListener并不需要关心。
OneViewHolder代码如下:
/**
* A ViewHolder that auto cast the data, from Object to the type you define
* @param the data type you define
*/
public abstract class OneViewHolder extends RecyclerView.ViewHolder {
public OneViewHolder(View itemView) {
super(itemView);
}
public OneViewHolder(ViewGroup parent, int layoutRes) {
super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
}
void bindView(int position, Object o){
bindViewCasted(position, (D) o);
}
protected abstract void bindViewCasted(int position, D d);
}
OneViewHolder继承自RecyclerView.ViewHolder,它将具体数据与视图绑定。由于数据在OneAdapter中都是Object类型,为了调用者方便,这里利用泛型自动对数据进行了强制类型转换。至于绑定视图封装了两个方法:
- OneViewHolder(ViewGroup parent, int layoutRes) 方法用于直接传入ItemView的布局资源,用于大多数情况。
- OneViewHolder(View itemView) 方法用于ItemView是自定义View的情况(此时要注意自定义View的LayoutParams)。
到这里,主要代码就讲完了。
示例
-
简单列表
public class SimpleListActivity extends AppCompatActivity {
OneAdapter oneAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(parent, R.layout.item_text){
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(R.id.text);
text.setText(s);
}
};
}
});
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
requestData();
}
private void requestData() {
List data = new ArrayList<>();
for(int i = 'A'; i <= 'z'; i++) {
data.add(" " + (char)i);
}
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
- 带Header和Footer的列表
public class HeaderFooterActivity extends AppCompatActivity {
RecyclerView recyclerView;
OneAdapter oneAdapter;
View footerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == 0;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(parent, R.layout.item_text) {
@Override
protected void bindViewCasted(int position, Object o) {
TextView text = itemView.findViewById(R.id.text);
text.setText("This is header");
}
};
}
},
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return o instanceof String;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(parent, android.R.layout.simple_list_item_1) {
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(android.R.id.text1);
text.setText(s);
}
};
}
},
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == oneAdapter.getItemCount() - 1;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(footerView) {
@Override
protected void bindViewCasted(int position, Object o) {
}
};
}
}
);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
initFooterView();
requestData();
}
private void initFooterView() {
footerView = LayoutInflater.from(this).inflate(R.layout.item_text, recyclerView, false);
((TextView)footerView.findViewById(R.id.text)).setText("This is footer");
}
private void requestData() {
List data = new ArrayList<>();
data.add(null);
for (int i = 'A'; i <= 'Z'; i++) {
data.add(" " + (char) i);
}
data.add(null);
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
扩展:Databinding支持
Databinding是Google推荐的做法,有了它就不需要写 findViewById() 语句了,还能直接在Layout文件中绑定数据。 使用方法也很简单,大家可以自己查一下相关教程。这里给OneAdapter添加Databinding支持。
对于OneAdapter来说,Databinding主要用于具体数据与视图绑定,也就是OneViewHolder中所做的。OneViewHolder有两个构造方法,分别对应自定义View和布局资源文件。对于自定义View来说,是否使用Databinding是调用者自己控制的。因此Databinding支持是针对使用布局资源文件的情况,这里封装了一个包装类OneViewHolderWrapper,用它替换OneViewHolder即可。
OneViewHolderWrapper代码:
/**
* A wrapper of OneViewHolder, supports data binding
* @param the type of the data
* @param the type of the ViewDataBinding
*/
public abstract class OneViewHolderWrapper{
private OneViewHolder oneViewHolder;
protected B binding;
public OneViewHolderWrapper(ViewGroup parent, int layoutRes){
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutRes, parent, false);
oneViewHolder = new OneViewHolder(binding.getRoot()) {
@Override
protected void bindViewCasted(int position, D d) {
OneViewHolderWrapper.this.bindViewCasted(position, d);
}
};
}
public OneViewHolder getOneViewHolder() {
return oneViewHolder;
}
protected abstract void bindViewCasted(int position, D d);
}
OneViewHolderWrapper中用D表示数据泛型,B表示ViewDataBinding泛型。binding对象用于绑定具体数据。
实际使用代码:
public class DataBindingActivity extends AppCompatActivity {
OneAdapter oneAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolderWrapper(parent, R.layout.item_person) {
@Override
protected void bindViewCasted(int position, Person person) {
binding.setPerson(person);
binding.executePendingBindings();
}
}.getOneViewHolder();
}
});
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
requestData();
}
private void requestData() {
List data = new ArrayList<>();
for(int i = 0; i <= 10; i++) {
data.add(new Person("Bill", 22));
data.add(new Person("Chris", 10));
data.add(new Person("David", 36));
}
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
需要OneViewHolder实例时,先创建包装类OneViewHolderWrapper实例,然后调用 getOneViewHolder() 方法从包装类中取得OneViewHolder实例。这样原有的OneAdapter和OneListener都直接兼容。
扩展:下拉刷新和加载更多
用SwipeRefreshLayout和FooterView实现了简单的下拉刷新和加载更多功能,这是对OneAdapter的简单扩展。没有加入EmptyView,因为EmptyView可以完全在外部控制。
public class RecyclerLayout extends SwipeRefreshLayout implements OnRefreshListener, LoadingLayout.OnLoadingListener {
private RecyclerView recyclerView;
private LoadingLayout loadingLayout;
private OneAdapter oneAdapter;
private GridLayoutManager gridLayoutManager;
private OnRefreshListener onRefreshListener;
private LoadingLayout.OnLoadingListener onLoadingListener;
public RecyclerLayout(Context context) {
this(context, null);
}
public RecyclerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOnRefreshListener(this);
loadingLayout = new LoadingLayout(context);
gridLayoutManager = new GridLayoutManager(context, 1);
recyclerView = new RecyclerView(context);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
int lastVisibleItemPosition;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == oneAdapter.getItemCount() - 1 - 1) {
// load more
onLoading();
}
}
});
addView(recyclerView);
}
public void init(final OneAdapter oneAdapter, OnRefreshListener onRefreshListener, LoadingLayout.OnLoadingListener onLoadingListener){
this.recyclerView.setAdapter(oneAdapter);
this.oneAdapter = oneAdapter;
this.oneAdapter.getListeners().add(0, new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == oneAdapter.getItemCount() - 1;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(loadingLayout) {
@Override
protected void bindViewCasted(int position, Object o) {
//ignore
}
};
}
});
if(onRefreshListener == null){
setEnabled(false);
}
this.onRefreshListener = onRefreshListener;
this.onLoadingListener = onLoadingListener;
}
@Override
public void onRefresh() {
if(onRefreshListener != null){
onRefreshListener.onRefresh();
}
}
@Override
public void onLoading() {
if(onLoadingListener != null && !isRefreshing() && !isLoading() && !isNoMore()){
onLoadingListener.onLoading();
setLoading(true, isNoMore());
}
}
public void setData(List> data, boolean hasMore){
data.add(null);
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
setRefreshing(false);
setLoading(false, !hasMore);
}
public void addData(List> data, boolean hasMore){
List cur = oneAdapter.getData();
if(!cur.isEmpty()){
cur.remove(cur.size() - 1);
}
data.add(null);
oneAdapter.addData(data);
oneAdapter.notifyDataSetChanged();
setLoading(false, !hasMore);
}
private boolean isNoMore(){
return loadingLayout.isNoMore();
}
private boolean isLoading(){
return loadingLayout.isLoading();
}
private void setLoading(boolean loading, boolean isNoMore){
loadingLayout.setLoading(loading, isNoMore);
}
}
实际使用如下:
public class RefreshActivity extends AppCompatActivity {
RecyclerLayout recyclerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recyclerLayout = new RecyclerLayout(this);
setContentView(recyclerLayout);
OneAdapter oneAdapter = new OneAdapter(
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(parent, R.layout.item_text) {
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(R.id.text);
text.setText(s);
}
};
}
}
);
recyclerLayout.init(oneAdapter,
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData();
}
},
new LoadingLayout.OnLoadingListener() {
@Override
public void onLoading() {
requestMoreData();
}
}
);
recyclerLayout.setRefreshing(true);
requestData();
}
int page;
private void requestData() {
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
List data = new ArrayList<>();
for (int i = 'A'; i <= 'Z'; i++) {
String s = (char) i + " " + System.nanoTime();
data.add(s);
}
page = 0;
recyclerLayout.setData(data, page++ < 2);
}
}, 1000);
}
private void requestMoreData() {
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
List data = new ArrayList<>();
for (int i = 'A'; i <= 'Z'; i++) {
String s = (char) i + " " + System.nanoTime();
data.add(s);
}
recyclerLayout.addData(data, page++ < 2);
}
}, 1000);
}
}
代码结构
- 实现普通或多种类型的RecyclerView,使用base包中的类即可;
- 如果需要Databinding支持,加入databinding包中的类;
- 如果需要下拉刷新和加载更多,可以参考refresh包中的实现。
Github地址:https://github.com/rome753/OneAdapter
完整代码和Demo示例都在这里,欢迎Fork和Star哦。