android收集IT技术设计模式

MVVM架构之自动增删改的极简RecycleView的实现

2016-12-22  本文已影响2497人  何时夕
介绍图

先上个源代码的链接:https://github.com/whenSunSet/MVVMRecycleView

RecycleView是Google替代ListView的一种方案,其有着很高的解耦度,让许多开发者抛弃了以往的ListView,那么RecycleView在MVVM架构下又该怎么实现呢?如何实现单条item刷新以及增减Item的自动刷新呢?今天我就要给大家带来一种方便的高解耦度的解决方案。

1.了解几个工具类

我们先来看几个我制作的工具类,这几个工具类可以一直复用。为啥要介绍他们呢,当然是为了让大家更好的了解内部的机制,在出现坑的时候不至于找不到解决方案,哈哈(当然我使用了这么久还没发现有坑)!

public class LayoutManager {

protected LayoutManager() {
}

public interface LayoutManagerFactory {
    RecyclerView.LayoutManager create(RecyclerView recyclerView);
}

public static LayoutManagerFactory linear() {
    return new LayoutManagerFactory() {
        @Override
        public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
            return new LinearLayoutManager(recyclerView.getContext());
        }
    };
}

public static LayoutManagerFactory linear(@Orientation final int orientation, final boolean reverseLayout) {
    return new LayoutManagerFactory() {
        @Override
        public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
            return new LinearLayoutManager(recyclerView.getContext(), orientation, reverseLayout);
        }
    };
}

public static LayoutManagerFactory grid(final int spanCount) {
    return new LayoutManagerFactory() {
        @Override
        public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
            return new GridLayoutManager(recyclerView.getContext(), spanCount);
        }
    };
}

public static LayoutManagerFactory grid(final int spanCount, @Orientation final int orientation, final boolean reverseLayout) {
    return new LayoutManagerFactory() {
        @Override
        public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
            return new GridLayoutManager(recyclerView.getContext(), spanCount, orientation, reverseLayout);
        }
    };
}

public static LayoutManagerFactory staggeredGrid(final int spanCount, @Orientation final int orientation) {
    return new LayoutManagerFactory() {
        @Override
        public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
            return new StaggeredGridLayoutManager(spanCount, orientation);
        }
    };
}

@IntDef({LinearLayoutManager.HORIZONTAL, LinearLayoutManager.VERTICAL})
@Retention(RetentionPolicy.SOURCE)
public @interface Orientation {
}

}

这个类中有一个内部累和一个内部接口,接下来我将来介绍这个类。

- 1.先来看看内部类ItemView:该类有两个int字段,bindingVariable储存实例在BR中生成的id,layoutRes储存所需xml文件的id。其他的几个方法都是为这两个字段服务的,比较简单,大家看看应该就了解了。
- 2.再来看看内部接口ItemViewSelector<T>:我们前面的内部类,只能提供一个布局给item,如果我的item有许多个布局怎么办的,这个接口就是用来解决这个问题。
    - 1.select(ItemView itemView, int position, T item):这个方法是开发者自己实现的,item这个字段是咱么列表Item的ViewModel,里面储存着到底当前列表item的布局type。所以我们只要用判断item中布局的种类,然后调用itemView的of或者set方法,就能对列表item的布局进行选择。**其实总的说就是根据字段item中的参数来构造出不同的ItemView**
    - 2.viewTypeCount():这个方法很简单,就是返回一下列表item所有布局的种类。
- 3.最后看看ItemViewArg<T>:该类中储存着ItemView和ItemViewSelector<T>的实体,我们在开发的时候只要传入一个实例另一个会自动生成。
    - 这其中最重要的一个方法就是select(int position, T item),大家先记着这个方法,在后面我们整个流程串起来了之后,会讲到这个方法。        

让我来解释一下这几个方法:
- 1.setAdapter():这一个方法中arg是在xml中必须设置的字段,其他都是可选字段,
- 1.arg我们在前面介绍过是为item提供界面id的实例。
- 2.items是为列表提供数据的实例。
- 3.animator是为item的增加删除提供动画的实例。
- 4.decor是为列表提供分割线的实例。
- 5.当然大家还可以为RecycleView设置更多的初始化的实例。
- 2.setLayoutManager():我们前面提供了LayoutManager这个类来生成各种列表排布情况,这方法xml中的字段的构造字段函数。
- 3.两个toItemViewArg()是转换器,能够将ItemView和ItemViewSelector<>转换成ItemViewArg(),这样在xml中itemView字段中也可以填写ItemView和ItemViewSelector<>类型的实例了。

public class BindingRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final Object DATA_INVALIDATION = new Object();

@NonNull
private final ItemViewArg<T> itemViewArg;//item界面的layout,通过databing设置
private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this);//数据list变化的时候的回调,设置在数据list中,如果list是ObservableList
private List<T> items;//数据的list
private LayoutInflater inflater;//初始化item 界面;
// Currently attached recyclerview, we don't have to listen to notifications if null.
@Nullable
private RecyclerView recyclerView;

public BindingRecyclerViewAdapter(@NonNull ItemViewArg<T> arg) {
    this.itemViewArg = arg;
}

@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int layoutId) {
    if (inflater == null) {
        inflater = LayoutInflater.from(viewGroup.getContext());
    }
    ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutId, viewGroup, false);
    final RecyclerView.ViewHolder holder=new BindingViewHolder(binding);
    binding.addOnRebindCallback(new OnRebindCallback() {
        @Override
        public boolean onPreBind(ViewDataBinding binding) {
            return recyclerView != null && recyclerView.isComputingLayout();
        }

        @Override
        public void onCanceled(ViewDataBinding binding) {
            if (recyclerView == null || recyclerView.isComputingLayout()) {
                return;
            }
            int position = holder.getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                notifyItemChanged(position, DATA_INVALIDATION);
            }
        }
    });
    return holder;
}

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    T item = items.get(position);
    ViewDataBinding binding = DataBindingUtil.getBinding(viewHolder.itemView);
    if (itemViewArg.bindingVariable() != ItemViewArg.ItemView.BINDING_VARIABLE_NONE) {
        boolean result = binding.setVariable(itemViewArg.bindingVariable(), item);
        if (!result) {
            Utils.throwMissingVariable(binding, itemViewArg.bindingVariable(), itemViewArg.layoutRes());
        }
        binding.executePendingBindings();
    }
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
    if (isForDataBinding(payloads)) {
        ViewDataBinding binding = DataBindingUtil.getBinding(holder.itemView);
        binding.executePendingBindings();
    } else {
        super.onBindViewHolder(holder, position, payloads);
    }
}    

public void setItems(@Nullable List<T> items) {
    if (recyclerView != null) {
        if (this.items instanceof ObservableList) {
            ((ObservableList<T>) this.items).removeOnListChangedCallback(callback);
        }
        if (items instanceof ObservableList) {
            ((ObservableList<T>) items).addOnListChangedCallback(callback);
        }
    }
    this.items = items;
    notifyDataSetChanged();
}

@Override
public int getItemCount() {
    return items == null ? 0 : items.size();
}

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    if (this.recyclerView == null && items != null && items instanceof ObservableList) {
        ((ObservableList<T>) items).addOnListChangedCallback(callback);
    }
    this.recyclerView = recyclerView;
}

@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
    if (this.recyclerView != null && items != null && items instanceof ObservableList) {
        ((ObservableList<T>) items).removeOnListChangedCallback(callback);
    }
    this.recyclerView = null;
}

@Override
public int getItemViewType(int position) {
    itemViewArg.select(position, items.get(position));
    return itemViewArg.layoutRes();
}

@Override
public long getItemId(int position) {
    return itemViewArg.layoutRes();
}

private boolean isForDataBinding(List<Object> payloads) {
    if (payloads == null || payloads.size() == 0) {
        return false;
    }
    for (int i = 0; i < payloads.size(); i++) {
        Object obj = payloads.get(i);
        if (obj != DATA_INVALIDATION) {
            return false;
        }
    }
    return true;
}

private static class BindingViewHolder extends RecyclerView.ViewHolder {
    public BindingViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
    }
}

private static class WeakReferenceOnListChangedCallback<T> extends ObservableList.OnListChangedCallback<ObservableList<T>> {
    final WeakReference<BindingRecyclerViewAdapter<T>> adapterRef;

    WeakReferenceOnListChangedCallback(BindingRecyclerViewAdapter<T> adapter) {
        this.adapterRef = new WeakReference<>(adapter);
    }

    @Override
    public void onChanged(ObservableList sender) {
        BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
        if (adapter == null) {
            return;
        }
        Utils.ensureChangeOnMainThread();
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) {
        BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
        if (adapter == null) {
            return;
        }
        Utils.ensureChangeOnMainThread();
        adapter.notifyItemRangeChanged(positionStart, itemCount);
    }

    @Override
    public void onItemRangeInserted(ObservableList sender, final int positionStart, final int itemCount) {
        BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
        if (adapter == null) {
            return;
        }
        Utils.ensureChangeOnMainThread();
        adapter.notifyItemRangeInserted(positionStart, itemCount);
    }

    @Override
    public void onItemRangeMoved(ObservableList sender, final int fromPosition, final int toPosition, final int itemCount) {
        BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
        if (adapter == null) {
            return;
        }
        Utils.ensureChangeOnMainThread();
        for (int i = 0; i < itemCount; i++) {
            adapter.notifyItemMoved(fromPosition + i, toPosition + i);
        }
    }

    @Override
    public void onItemRangeRemoved(ObservableList sender, final int positionStart, final int itemCount) {
        BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
        if (adapter == null) {
            return;
        }
        Utils.ensureChangeOnMainThread();
        adapter.notifyItemRangeRemoved(positionStart, itemCount);
    }
}

}

这个类中由两个内部类和其自己组成

2.在MVVM下构造极简的RecycleView

我们上面已经把我们接下来要使用的工具类介绍完了,以上的工具类之后基本不需要变化代码,所以在去除工具类之后我们的自动增删改的RecycleView将会只剩业务代码。

在讲解具体代码之前,我提几点我直接对于MVVM这个架构的看法,方便大家了解我之后的代码。

好了废话不多讲了让我们来看看逻辑的代码吧。

上面的代码很简单主要有以下几点。

- 1.使用variable标签引入了ViewModel和UiViewModel这两个实例。
- 2.设置了3个Button和1个EditText。
- 3.在RecycleView中使用了我在工具类BindingAdapters中定义的items和itemView这两个字段来为RecycleView设置初始化变量。

MainActivity中的代码也很简单
- 1.在Activity中获取ViewDatabinding并设置ViewModel和UiViewModel。
- 2.在内部类UiViewModel中,设置了各种监听器。
- 3.值得注意的是:虽然我在UiViewModel中设置了监听器,但是如果涉及到数据存储以及业务逻辑的话,还是需要在ViewModel中进行的,所以我在UiViewModel中传入了具体的MainActivity的引用,由于UiViewModel只是Activity中监听器的集合,并不是真正的ViewModel,所以这里并不违反我之前说的观点。还有就是虽然这样的写法看起来有点绕,但是这样所带来的代码条理性更好。

RecycleView的ViewModel由其自身和一个内部类ItemViewModel构成,ItemViewModel不必多说,就是RecycleView中每个item的ViewModel,值得注意的是,当一个RecycleView的内容比较多的时候,我们可以拆分ViewModel,即将ItemViewModel独立成为一个新类。这个操作在一项目的后期操作起来也很简单,不过一般的项目大部分的逻辑都在ItemViewModel中,所以我才使用了内部类的方式。

- 1.该ViewModel中有几个成员变量。
    - 1.MainActivity:这个在大家的项目中推荐像我前面说的那样传入BaseActivity,进行解耦。
    - 2.ItemViewArg.ItemViewSelector< MainActivityItemViewModel > :这个接口是我前面介绍的工具类,其内部选择布局的方法需要开发者自己实现,**我们可以看见这个接口的泛型就是ItemViewModel**
    - 3.ObservableList< MainActivityItemViewModel >:这个就是我们的数据源,其泛型也是ItemViewModel。
- 2.可以看见我在ViewModel的构造函数中初始化了ItemViewArg.ItemViewSelector和ObservableList。
    -  1.ItemViewArg.ItemViewSelector有两个需要被重写的方法
        - 1.select(ItemViewArg.ItemView itemView, int position, MainActivityItemViewModel item):**一般情况下我们会在ItemViewModel中放置该item到底使用哪个界面的tag**,所以在这里我们就可以使用item这个参数来选择layoutId。
        - 2.viewTypeCount():这个方法就是返回目前到底有几种布局。
    - 2.我使用静态数据来初始化ObservableList,在这里大家在项目中会使用网络的数据。然后根据网络的数据来构造ItemViewModel。  
- 3.后面有几个方法分别是add,delete和update。这几个方法是在UiViewModel中的监听器中调用的 ,我们可以看见我只是根据position增删改了ObservableList中的数据。
- 4.最后来看看ItemViewModel中的代码,可以看见里面的代码也很简单
    - 1.type:用来判断item到底是哪种布局。
    - 2.ObservableField< String >:item的数据。

以上就是极简RecycleView自动增删改的全部代码,可以说除了工具类之外几乎所有的代码都是业务逻辑,实现起来也非常简单,大家如果不想了解具体细节可以直接将几个工具类复制到自己项目中就能使用。 这是MVVM架构前奏的第二波,大家可以看看我之前发的databinding源码解析,满满的相信干货大家一定会喜欢。

上一篇下一篇

猜你喜欢

热点阅读