多级RecyclerView嵌套下的下载进度刷新方案

  • 最近在做一个类似于应用市场的项目,其中首页用到的嵌套RecyclerView,由于涉及到下载进度刷新的问题,在此记录一下
  • 原理简介
    其实对于这种嵌套的RecyclerView中的进度刷新问题,其最本质的问题是找到需要刷新的控件然后把进度刷上去,根据这个思路我们就有了以下的一些代码。
  • 实现
    首先一般针对稍微复杂的布局,我们可以通过RecyclerView.Adapter中的getItemViewType来实现,该方法的返回值即onCreateViewHolder回调方法中的viewType
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {}

这里不做过多的赘述;
下面到重点了,假设我现在有一个观察者(该观察者是需要刷新进度的UI,既可以是Activity也可以是Fragment)观察到下载进度变化了,它大约是这样的

DataWatcher mDownloadObserver = new DataWatcher() {
    @Override
    public void notifyUpdate(DownloadInfo downloadInfo) {
        //此处处理下载进度刷新的逻辑     
    }
};

这个观察者中包含了一个嵌套的RecyclerView.那我的思路是先通过外层RecyclerView的LayoutManager拿到最外层的在屏幕上的可见的Item.
当拿到Item时,我们去遍历这些Item,通过ItemView绑定Tag的方式拿到其对应的ViewHolder.
这里我通过定义一个BaseViewHolder的方式,让每一个类型的Holder都有其对应的类型,然后再通过其暴露出去的获取类型接口来知道这个Item到底是哪种类型的Item然后将其强转成具体的类型

public class BaseViewHolder extends RecyclerView.ViewHolder {
    private int mType;
    public int getType() {
        return mType;
    }
    /**
    *这里的type用来标识该ViewHolder的具体类型,便于在UI中遍历所有ItemView的时候可以将其强转成具体的类型
    */
    public BaseViewHolder(View itemView,int type) {
        super(itemView);
        /*setTag的目的是为了将ViewHolder和ItemView绑定起来,便于遍历ItemView的时候拿到其对应的ViewHolder*/
        itemView.setTag(this);
        this.mType = type;
    }
}

遍历所有可见的Item,并根据Type将其强转成对应的类型

int count = mLayoutManager.getChildCount();
    for (int k = 0; k < count; k++) {
        View itemView = mLayoutManager.getChildAt(k);
        if (itemView != null && itemView.getTag() != null ) {
            BaseViewHolder viewHolder = (BaseViewHolder) itemView.getTag();
            switch (viewHolder.getType()) {
                case ChoicenessItemType.VERTICAL_LIST:
                    handleVerticalListUpdate(downloadInfo, (VerticalListVH) viewHolder);
                    break;
                case ChoicenessItemType.GRID:
                    handleGridUpdate(downloadInfo, (GridVH) viewHolder);
                    break;
                case ChoicenessItemType.HORIZONTAL_LIST:
                    handleHorizontalUpdate(downloadInfo, (HorizontalListVH) viewHolder);
                    break;
                default:
                break;
            }
        }
    }

在完成上述步骤以后,我们基本已经将工作完成了一大半,现在我们只需要再通过内部被嵌套的子RecyclerView的LayoutManager拿到其所有显示在屏幕上的的Item,然后依旧通过给子ItemView绑定tag的方式来找到我们真正需要刷新的控件。这里有很多种方式来实现,我这里讲一下我在项目中的做法,因为我们下载的时候会回调被下载的应用信息过来,其中有包名这一项,那么我就可以通过RecyclerView在创建视图的时候将应用信息和itemView通过setTag的方式绑定,具体操作是在RecyclerView中的onBindViewHolder中绑定,然后当观察到进度变化时,判断包名相等的即我们所需要的控件,

@Override
public void onBindViewHolder(@NonNull final VerticalListSubVH holder, int position) {
    if (holder == null) {
        return;
    }
    //这里的getItem拿到的就是应用信息DownloadInfo
    holder.downloadBtn.setTag(getItem(position));
}
private void handleVerticalListUpdate(DownloadInfo downloadInfo, VerticalListVH verticalListVH) {
    RecyclerView.LayoutManager layoutManager = verticalListVH.gameListRv.getLayoutManager();
        int size = layoutManager.getChildCount();
        for (int i = 0; i < size; i++) {
            View view = layoutManager.getChildAt(i);
            VerticalListSubVH verticalListSubVH = (VerticalListSubVH) view.getTag();
            if (verticalListSubVH != null && verticalListSubVH.downloadBtn.getTag() != null) {
                DownloadInfo tag = (DownloadInfo)verticalListSubVH.downloadBtn.getTag();
                if (TextUtils.equals(downloadInfo.getPackageName(), tag.getPackageName())) {
                    long progress = downloadInfo.getProgress();
                    long fileLength = downloadInfo.getFileLength();
                     int p = (int) (progress * 100 / fileLength);
                    //这里的p即需要刷新的进度
                    //verticalListSubVH.downloadBtn 该控件即我们要找的需要刷新的控件
                    }
                }
            }
        }
    }
  • Tips
    最后有个需要注意的地方,我们需要在每个RecyclerView中的onBindViewHolder方法中去初始化按钮的状态,比如当前是下载中还是暂停或者安装等