在《RecyclerView体验简介》里已经介绍了RecyclerView目前的可用情况,也提供了一些文章的链接来了解这个新的Android视图的使用方法,API的调用方式。但由于RecyclerView的Adapter需要开发者重新实现,这相当于一次完整的重构了。本文就来介绍一下我在尝鲜RecyclerView所使用到的非常规手段,如果您也只是想尝试一下把原有的程序转到RecyclerView下看看效果,也许本文会有一些帮助。
ViewHolder-挂羊头卖狗肉
之前已经强调RecyclerView强制要求使用ViewHolder模式。但即使是强制,也可以采用“上有政策,下有对策”,敷衍地实现一个子类就好:
[codesyntax lang=”java” lines=”normal”]
public class DummyViewHolder extends RecyclerView.ViewHolder {
public DummyViewHolder(View itemView) {
super(itemView);
}
}
[/codesyntax]
至于缓存子项视图的内部视图到变量上,就先免了。
对于比较复杂的ListView子视图应该都会遵循使用ViewHolder,只是没有具体继承与某个基类。所以只要将那些自定义的ViewHolder显示继承RecyclerView.ViewHolder,再在构造函数里调用一下super(view);就好。
Adapter-新瓶装旧酒
有了ViewHolder,就可以开始创建RecyclerView的Adapter<VH extends ViewHolder>了。首先上整个实现的代码,再来慢慢解释这恶劣的手段。
[codesyntax lang=”java” lines=”normal”]
public class AdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// Any negative value could be invalid, as position could not less than 0
private static final int INVALID_VALUE = -9999;
private Adapter mAdapter;
private int mRecentRequestedType = INVALID_VALUE;
private int mPositionOfRequestedType = INVALID_VALUE;
public AdapterWrapper(Adapter adapter) {
mAdapter = adapter;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (mRecentRequestedType != viewType) {
throw new IllegalStateException("View types doe not match. "
+ "Saved: " + mRecentRequestedType + ", wanted: " + viewType);
}
if (mPositionOfRequestedType == INVALID_VALUE) {
throw new IllegalStateException("Position is unknown");
}
final RecyclerView.ViewHolder holder = onCreateViewHolder(mPositionOfRequestedType, viewGroup);
mRecentRequestedType = INVALID_VALUE;
mPositionOfRequestedType = INVALID_VALUE;
return holder;
}
public RecyclerView.ViewHolder onCreateViewHolder(int position, ViewGroup viewGroup) {
final View view = getView(position, viewGroup);
final Object tag = view.getTag();
if (tag instanceof RecyclerView.ViewHolder) {
return (RecyclerView.ViewHolder) tag;
}
return new DummyViewHolder(getView(position, viewGroup));
}
protected View getView(int position, ViewGroup viewGroup) {
return mAdapter.getView(position, null, viewGroup);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
// BindView suppose not use the ViewGroup, hence 'null' is passed here
mAdapter.getView(i, viewHolder.itemView, null);
}
@Override
public int getItemCount() {
return mAdapter.getCount();
}
@Override
public int getItemViewType(int position) {
mPositionOfRequestedType = position;
return mRecentRequestedType = mAdapter.getItemViewType(position);
}
@Override
public long getItemId(int position) {
return mAdapter.getItemId(position);
}
public Object getItem(int position) {
return mAdapter.getItem(position);
}
/**
* This class provides concrete object of abstract {@link RecyclerView.ViewHolder}.
*/
static class DummyViewHolder extends RecyclerView.ViewHolder {
public DummyViewHolder(View itemView) {
super(itemView);
}
}
}
[/codesyntax]
要让RecyclerView的Adapter兼容ListView的Adapter最大的麻烦是创建视图时得到的参数只有viewType而不是以往的子项的index。不过翻看源码可以看到在调用新建ViewHolder函数时总是会去调用getItemViewTyp():
[codesyntax lang=”java” lines=”normal”]
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
[/codesyntax]
因此就可以在getItemViewType()被调用时缓存下子项的index然后在接着立刻发生创建方法中拿来使用,这也是就是代码中mPositionOfRequestedType变量的意义。不过一旦Google改变这段源码,将子项目类型存放在局部变量中,该黑科技可能就会是有效了。但是看这段的实现方式,也许也是为了保证在其内部也能通过这个黑科技来偷懒吧。
[codesyntax lang=”java” lines=”normal”]
public int getItemViewType(int position) {
mPositionOfRequestedType = position;
return mRecentRequestedType = mAdapter.getItemViewType(position);
}
[/codesyntax]
[codesyntax lang=”java” lines=”normal”]
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (mRecentRequestedType != viewType) {
throw new IllegalStateException("View types doe not match. "
+ "Saved: " + mRecentRequestedType + ", wanted: " + viewType);
}
if (mPositionOfRequestedType == INVALID_VALUE) {
throw new IllegalStateException("Position is unknown");
}
final RecyclerView.ViewHolder holder = onCreateViewHolder(mPositionOfRequestedType, viewGroup);
mRecentRequestedType = INVALID_VALUE;
mPositionOfRequestedType = INVALID_VALUE;
return holder;
}
[/codesyntax]
另外对于返回ViewHolder的问题,针对前面定义的方式,就可以相应的得到ViewHolder对象:如果原来存放在tag里的ViewHolder已经继承了RecyclerView.ViewHolder,就可以直接重用它,否则就创建一个什么都不做的DummyViewHolder。
[codesyntax lang=”java” lines=”normal”]
public RecyclerView.ViewHolder onCreateViewHolder(int position, ViewGroup viewGroup) {
final View view = getView(position, viewGroup);
final Object tag = view.getTag();
if (tag instanceof RecyclerView.ViewHolder) {
return (RecyclerView.ViewHolder) tag;
}
return new DummyViewHolder(getView(position, viewGroup));
}
[/codesyntax]
至于剩下的任务,就全都转给了原来使用在ListView上的Adapter。
[codesyntax lang=”java” lines=”normal”]
public AdapterWrapper(Adapter adapter) {
mAdapter = adapter;
}
[/codesyntax]
LinearLayoutManager-鱼目混珠
RecyclerView的呈现形式非常自由,要让它变得跟ListView一样,就要用LinearLayoutManager,创建方式也很简单:
[codesyntax lang=”java” lines=”normal”]
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
[/codesyntax]
RecyclerView-乱锅炖
最后要把上边所有的东西都糅合成一起在RecyclerView上呈现出来。首先要把在Layout定义中把ListView替换成RecyclerView
[codesyntax lang=”xml” lines=”normal”]
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
[/codesyntax]
之后在代码中将之前的一切装配进去:
[codesyntax lang=”java” lines=”normal”]
mRecyclerView.setAdapter(new AdapterWrapper(listAdapter)); mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
[/codesyntax]
剩下的则还是原来使用的代码。
黑科技介绍完毕,不过也看到对于这些手段所用的描绘都不是好的词,毕竟这不是应用RecyclerView所使的常规方式,部分代码依赖于当前RecyclerView库的具体实现方式,ViewHolder也没有尽到责任。所以如果只是想感受ListView迁移到RecyclerView的效果,可以用这里提供的方式来做个原型展示。当真正要使用RecyclerView时,还是应该按照其设计要求去实现每个类和接口。