【worktile开发小记】复杂RecyclerView的拆分

本文记录了在Worktile企业版任务详情界面中,如何将复杂的RecyclerView拆分成小模块,以保持代码结构清晰。通过创建一个ModularRecyclerViewAdapter作为桥梁,结合Extension实现各个模块的独立管理,解决了Adapter文件过于庞大和混乱的问题。文章详细介绍了实现步骤、基本原则和解决视图类型冲突的策略。
摘要由CSDN通过智能技术生成

【worktile开发小记】复杂RecyclerView的拆分

引言:

如图所示:这是“worktile企业版”的任务详情界面,这个界面有许多元素,并且需要滚动,如果这些统统写在一个Adapter中,那么这个Adapter文件将可能变得混乱且庞大。所以我决定将这样一个复杂页面拆分成小的模块,但是这个Activity的布局依然保持只有一个RecyclerView,以此保证工程结构的清晰和每个文件不会过于臃肿。

代码及demo点击这里查看

任务详情设计稿

这是“worktile企业版”任务模块中任务详情的工程结构

任务详情目录结构

注意:下文中出现的TaskDetailAdapter和ModularRecyclerViewAdapter是同一个类,之所以有两个名字是因为一开始是在worktile企业版任务模块做的这件事情,后来发现这件事情不仅可以用在任务模块,还可以放在其他很多地方,所以就提了出来,改名叫modularRecyclerViewAdapter,大家看这篇博客的时候注意一下就可以了

How-To

  1. 创建一个类ImplExtension继承于Extension,并实现Extension中abstract方法。
  2. ImplExtension的内部类ViewHolder继承于ModularRecyclerAdapter.ViewHolder
  3. Activity中创建ModularRecyclerViewAdapter对象,并将各种Extension按照顺序传入ModularRecyclerViewAdapter的构造方法中
  4. recylerview.setAdapter()

实现

基本原则

  1. 拆分出的模块尽可能按照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);
    }

第一次写博客,我总感觉没怎么太说清楚,看官见谅,有事多交流。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值