【worktile开发小记】复杂RecyclerView的拆分
引言:
如图所示:这是“worktile企业版”的任务详情界面,这个界面有许多元素,并且需要滚动,如果这些统统写在一个Adapter中,那么这个Adapter文件将可能变得混乱且庞大。所以我决定将这样一个复杂页面拆分成小的模块,但是这个Activity的布局依然保持只有一个RecyclerView,以此保证工程结构的清晰和每个文件不会过于臃肿。
代码及demo点击这里查看
这是“worktile企业版”任务模块中任务详情的工程结构
注意:下文中出现的TaskDetailAdapter和ModularRecyclerViewAdapter是同一个类,之所以有两个名字是因为一开始是在worktile企业版任务模块做的这件事情,后来发现这件事情不仅可以用在任务模块,还可以放在其他很多地方,所以就提了出来,改名叫modularRecyclerViewAdapter,大家看这篇博客的时候注意一下就可以了
How-To
- 创建一个类ImplExtension继承于Extension,并实现Extension中abstract方法。
- ImplExtension的内部类ViewHolder继承于ModularRecyclerAdapter.ViewHolder
- Activity中创建ModularRecyclerViewAdapter对象,并将各种Extension按照顺序传入ModularRecyclerViewAdapter的构造方法中
- recylerview.setAdapter()
实现
基本原则
- 拆分出的模块尽可能按照Android提供的RecyclerViewAdapter的写法,以免其他同事增加学习成本
详细分析
public class ModularRecyclerViewAdapter extends RecyclerView.Adapter<ModularRecyclerViewAdapter.ViewHolder> {
private Extension[] mExtensions;
private int mExtensionsCount;
private HashMap<Integer, Extension> mIdExtensionMap = new HashMap<>();
private HashMap<Extension, Integer> mExtensionIdMap = new HashMap<>();
private ViewGroup mParent;
public ModularRecyclerViewAdapter(Extension... extensions) {
mExtensionsCount = extensions.length;
mExtensions = new Extension[mExtensionsCount];
for (int i = 0; i < mExtensionsCount; i++) {
mExtensions[i] = extensions[i];
mIdExtensionMap.put(i + 2, extensions[i]);
mExtensionIdMap.put(extensions[i], i + 2);
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
mParent = parent;
int viewTypeInExtension = 0;
Extension extension = mIdExtensionMap.get(globalViewType >> 24);
byte[] targets = new byte[4];
targets[0] = (byte) (globalViewType & 0xff);
targets[1] = (byte) ((globalViewType >> 8) & 0xff);
targets[2] = (byte) ((globalViewType >> 16) & 0xff);
targets[3] = (byte) 0;
byte digit;
for (int i = 0; i < targets.length; i++) {
digit = targets[i];
viewTypeInExtension += (digit & 0xFF) << (8 * i);
}
return extension.onCreateViewHolder(parent, viewTypeInExtension);
}
@Override
public void onBindViewHolder(ViewHolder holder, int globalPosition) {
SpecificExtension specificExtension = getSpecificExtensionByPosition(globalPosition);
specificExtension.onBindViewHolder(holder, specificExtension.mPositionInExtension);
}
@Override
public int getItemCount() {
int itemCount = 0;
for (int i = 0; i < mExtensionsCount; i++) {
itemCount += mExtensions[i].getItemCount();
}
return itemCount;
}
@Override
public int getItemViewType(int globalPosition) {
SpecificExtension specificExtension = getSpecificExtensionByPosition(globalPosition);
return specificExtension.getItemType(specificExtension.mPositionInExtension);
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
for (Extension extension: mExtensions) {
extension.onDetach(recyclerView);
}
}
public static abstract class ViewHolder<T> extends RecyclerView.ViewHolder {
public ViewHolder(ViewGroup layout) {
super(layout);
}
public ViewHolder(ViewGroup layout, T viewHolderContext) {
super(layout);
}
public T getViewHolderContext() {
return null;
}
}
private class SpecificExtension {
private Extension mExtension;
private int mPositionInExtension;
public SpecificExtension(Extension extension, int positionInExtension) {
mExtension = extension;
mPositionInExtension = positionInExtension;
}
public int getItemType(int positionInExtension) {
int viewTypeInExtension = mExtension.getItemViewType(positionInExtension);
int globalViewType;
if (viewTypeInExtension < 0) {
throw new IllegalArgumentException(
"when using ModularRecyclerViewAdapter, viewType must be a positive number");
} else if (viewTypeInExtension > 16777215) {
throw new IllegalArgumentException(
"when using ModularRecyclerViewAdapter, viewType can not greater than 16777215");
} else {
int temp = mExtensionIdMap.get(mExtension);
globalViewType = viewTypeInExtension + ((temp & 0xFF) << 24);
}
return globalViewType;
}
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewTypeInExtension) {
return mExtension.onCreateViewHolder(parent, viewTypeInExtension);
}
@SuppressWarnings("unchecked")
public void onBindViewHolder(ViewHolder viewHolder, int positionInExtension) {
String viewHolderClassName = viewHolder.getClass().getName().replace("$ViewHolder", "");
String extensionClassName = mExtension.getClass().getName();
if (!viewHolderClassName.equals(extensionClassName)) {
viewHolder = mExtension.onCreateViewHolder(mParent, mExtension.getItemViewType(positionInExtension));
}
mExtension.onBindViewHolder(viewHolder, positionInExtension);
}
}
public SpecificExtension getSpecificExtensionByPosition(int globalPosition) {
for (int extensionIndex = 0; extensionIndex < mExtensionsCount; extensionIndex++) {
globalPosition = globalPosition - mExtensions[extensionIndex].getItemCount();
if (globalPosition < 0) {
int positionInExtension = globalPosition + mExtensions[extensionIndex].getItemCount();
Extension extension = mExtensions[extensionIndex];
return new SpecificExtension(extension, positionInExtension);
}
}
return null;
}
}
public abstract class Extension<VH extends TaskDetailAdapter.ViewHolder> extends RecyclerView.Adapter<VH> {
public List<Object> mData = new ArrayList<>();
@Override
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
@Override
public abstract void onBindViewHolder(VH holder, int position);
@Override
public int getItemCount() { return mData.size(); }
@Override
public abstract int getItemViewType(int position);
public void onDataChanged(List<Object> data) {
mData = data;
}
public void onDetach(RecyclerView recyclerView) {}
}
从上述代码中,可以看到ModulerRecyclerViewAdapter继承于RecyclerView.Adapter,Extension也继承于RecyclerView.Adapter。但是ModulerRecyclerViewAdapter的作用并不是将数据源和RecyclerView绑定,而是起到bridge的作用,真正起到绑定作用的是各种Extension。ModulerRecyclerViewAdapter的bridge作用体现在它可以通过recyclerView中position找到这个item到底属于哪一个Extension(SpecificExtension),然后执行extension中的具体方法来实现adapter的作用。
例如:
@Override
public void onBindViewHolder(ViewHolder holder, int globalPosition) {
SpecificExtension specificExtension = getSpecificExtensionByPosition(globalPosition);
specificExtension.onBindViewHolder(holder, specificExtension.mPositionInExtension);
}
上述代码中的globalPosition就是item在recyclerView中的position,根据这个position获取到具体的extension,然后执行相应的方法。
但是,在Android的设计中,onCreateViewHolder是和position无关,而是和viewtype有关的,并且整个ModularRecyclerViewAdapter也确实需要知道每个item相对于recyclerview来说的viewType,所以必须要对viewType做特殊的处理。
我的做法是:
public int getItemType(int positionInExtension) {
int viewTypeInExtension = mExtension.getItemViewType(positionInExtension);
int globalViewType;
if (viewTypeInExtension < 0) {
throw new IllegalArgumentException(
"when using ModularRecyclerViewAdapter, viewType must be a positive number");
} else if (viewTypeInExtension > 16777215) {
throw new IllegalArgumentException(
"when using ModularRecyclerViewAdapter, viewType can not greater than 16777215");
} else {
int temp = mExtensionIdMap.get(mExtension);
globalViewType = viewTypeInExtension + ((temp & 0xFF) << 24);
}
return globalViewType;
}
这是SpecificExtension类中的getItemViewType方法,如果在这个方法内部只调用具体extension的getItemViewType方法,然后返回一个int值。
那么问题随之而来,假设我有两个Extension,FirstExtension和SecondExtension,FirstExtension中有viewType值1、2、3,SecondExtension中有viewType值1、2,那么传到ModularRecyclerView中,FirstExtension的viewType1、2就会和SecondExtension的viewType1、2冲突,以至于onCreateView和其他方法都会错误,因为他们不是根据真正的viewType来createViewHolder的。
为了解决这个问题,我就在extension.getItemViewType返回一个int值之后,对这个int值进行处理,将其最高位换成extension的ID (这个ID是在ModularRecyclerViewAdapter的构造方法中设置的,详见构造方法。并且这个值的有效位一般不会超过1个字节,因为不会有人一次性传255个extension)。需要使用时,再将globalViewType分解成最高位和后三位,这样无论是ModularRecyclerViewAdaper还是extension就都可以正确调用了。
分解如下:
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
mParent = parent;
int viewTypeInExtension = 0;
Extension extension = mIdExtensionMap.get(globalViewType >> 24);
byte[] targets = new byte[4];
targets[0] = (byte) (globalViewType & 0xff);
targets[1] = (byte) ((globalViewType >> 8) & 0xff);
targets[2] = (byte) ((globalViewType >> 16) & 0xff);
targets[3] = (byte) 0;
byte digit;
for (int i = 0; i < targets.length; i++) {
digit = targets[i];
viewTypeInExtension += (digit & 0xFF) << (8 * i);
}
return extension.onCreateViewHolder(parent, viewTypeInExtension);
}
附
第一次写博客,我总感觉没怎么太说清楚,看官见谅,有事多交流。