上一篇我们介绍了只用Databinding的方式快速实现了一个主从联动的组合自定义控件,今天我们要实现的是一个无限扩展的组织树控件。
先看效果:
效果介绍
不像网上的一下demo中看不中用,我的开源库开箱即用,可以直接用于生产环境,高内聚,低耦合,支持各个层级样式自定义,可以自由的加载不同的xml,修改viewHolder,支持异步加载子节点。完美的状态保持,activity重建也不会丢失状态。
设计思路
- 首先对数据进行建模,每一个层级抽象为一个Node,每一个Node包含若干个子Node,类似N叉树的形式。最顶端的Node为Root node。
public interface Node<T> extends Checkable {
boolean isLeaf();
boolean isLeafParent();
boolean isRoot();
LiveData<List<T>> getItems();
}
注意,这里定义子节点是为了支持异步加载
- 自定义容器viewGroup,这个容器在适当的时间创建自身
public class KanaView extends FrameLayout {
private KanaPresenter mPresenter;
public KanaView(@NonNull Context context) {
super(context);
init();
}
public KanaView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public KanaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
public void setPresenter(KanaPresenter presenter) {
if (mPresenter != null && !mPresenter.equals(presenter)) {
mPresenter.destroy();
}
mPresenter = presenter;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mPresenter != null) {
mPresenter.destroy();
}
}
}
业务逻辑包含在Presenter里
public class KanaPresenter implements Presenter {
private final LifecycleOwner lifecycleOwner;
private final KanaView mKanaView;
private final RecyclerView rv;
private final Node rootNode;
private final Observer<List<? extends Node>> listObserver;
private final KanaPresenterFactory kanaPresenterFactory;
public KanaPresenter(KanaView parent, LifecycleOwner lifecycleOwner, Node root, KanaPresenterFactory factory) {
ViewGroup container = parent.findViewById(R.id.container);
if (container == null) {
LayoutInflater.from(parent.getContext()).inflate(R.layout.hof_kana_view, parent, true);
}
container = parent.findViewById(R.id.container);
kanaPresenterFactory = factory;
this.lifecycleOwner = lifecycleOwner;
rv = container.findViewById(R.id.rv1);
mKanaView = container.findViewById(R.id.kana);
mKanaView.removeAllViews();
this.rootNode = root;
if (rootNode.isLeafParent()) {
mKanaView.setVisibility(View.GONE);
ViewGroup.LayoutParams layoutParams = rv.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
rv.setLayoutParams(layoutParams);
//return;
}
listObserver = obtainListObserver(mKanaView, rv, rootNode);
rootNode.getItems().observe(lifecycleOwner, listObserver);
}
public void destroy() {
rootNode.getItems().removeObserver(listObserver);
}
/**
* 继承这个方法如果你想对adapter进行设置
*
* @return
*/
protected Observer<List<? extends Node>> obtainListObserver(KanaView mKanaView, RecyclerView rv, Node rootNode) {
return nodes -> {
Context context = mKanaView.getContext();
MOTypedRecyclerAdapter mAdapter = new MOTypedRecyclerAdapter();
rv.setLayoutManager(new LinearLayoutManager(context));
for (int i = 0; i < rv.getItemDecorationCount(); i++) {
rv.removeItemDecorationAt(i);
}
DividerItemDecoration decor = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL);
if (rootNode.isRoot()) {
decor.setDrawable(ContextCompat.getDrawable(context, android.R.drawable.divider_horizontal_bright));
} else {
decor.setDrawable(ContextCompat.getDrawable(context, R.drawable.hof_inset_left_divider));
}
rv.addItemDecoration(decor);
mAdapter.addDelegate(obtainDelegate(rootNode, this));
rv.setAdapter(mAdapter);
mAdapter.setDataSet(nodes);
for (Node node : nodes) {
if (!node.isLeaf() && node.isChecked().get()) {
born(node);
break;
}
}
};
}
/**
* 继承这个方法如果你想改变item的呈现方式
*
* @param root
* @param presenter
* @return
*/
protected MOTypedRecyclerAdapter.AdapterDelegate obtainDelegate(Node root, KanaPresenter presenter) {
return new MOTypedRecyclerAdapter.AdapterDelegate() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(MOTypedRecyclerAdapter adapter, ViewGroup parent) {
ViewDataBinding binding;
if (root.isRoot()) {
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.hof_list_item_tree_root, parent, false);
} else if (root.isLeafParent()) {
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.hof_list_item_tree_leaf, parent, false);
} else {
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.hof_list_item_tree_middle, parent, false);
}
return new BindingViewHolder<>(binding);
}
@Override
public void onBindViewHolder(MOTypedRecyclerAdapter moTypedRecyclerAdapter, RecyclerView.ViewHolder viewHolder, Object o) {
((BindingViewHolder) viewHolder).setItem(BR.item, o);
((BindingViewHolder) viewHolder).setItem(BR.presenter, presenter);
((BindingViewHolder) viewHolder).executePendingBindings();
}
@Override
public boolean isDelegateOf(Class<?> clazz, Object item, int position) {
return Node.class.isAssignableFrom(clazz);
}
};
}
protected void onLeafClick(Node node) {
node.toggleChecked();
}
public void onItemClick(Node node) {
if (node.isLeaf()) {
onLeafClick(node);
return;
}
if (node.isChecked().get()) {
return;
}
node.setChecked(true);
born(node);
}
private void born(Node node) {
mKanaView.setPresenter(kanaPresenterFactory.create(mKanaView, lifecycleOwner, node, kanaPresenterFactory));
}
}
注意这里使用了Factory的设计模式,因为Presenter的创建是根据用户的点击操作和运行状态,我们不直接创建Presenter的实例,而是创建一个Factory,让Factory在适当的时机根据当前运行状态创建对应的Presenter,这样我们可以复写Factory的方式来动态管理我们的Presenter,这也是Java 里Ioc的思想体现
public interface KanaPresenterFactory {
KanaPresenter create(KanaView mKanaView, LifecycleOwner lifecycleOwner, Node node, KanaPresenterFactory factory);
}
如何使用
参加demo
我们首先将我们的的Item继承Node
public class MyNode implements Node {
private final String name;
private final LiveData<List<? extends Node>> items;
private final int deep;
private final ObservableBoolean checked;
private final Node parent;
public MyNode(Node parent, String name, int deep) {
this.parent = parent;
this.name = name;
this.deep = deep;
this.checked = new ObservableBoolean();
this.items = new MediatorLiveData<>();
LiveData<List<Node>> source = LiveDataReactiveStreams.fromPublisher(s -> {
List<Node> ret = new ArrayList<>();
for (int i = 0; i < 20; i++) {
ret.add(new MyNode(this, name+"-" + i, deep + 1));
}
s.onNext(ret);
});
((MediatorLiveData<List<? extends Node>>) this.items).addSource(source, nodes -> {
((MediatorLiveData<List<? extends Node>>) this.items).setValue(nodes);
((MediatorLiveData<List<? extends Node>>) this.items).removeSource(source);
});
}
public String getName() {
return name;
}
@NonNull
@Override
public String toString() {
return name;
}
@Override
public boolean isLeaf() {
return deep >= 5;
}
@Override
public boolean isLeafParent() {
return deep >= 4;
}
@Override
public boolean isRoot() {
return parent == null;
}
@Override
public LiveData<List<? extends Node>> getItems() {
return items;
}
@Override
public void setChecked(boolean value) {
if (checked.get() == value) {
return;
}
notifyParent(checked.get(), value);
checked.set(value);
}
private void notifyParent(boolean pre, boolean now) {
if (parent == null) {
return;
}
((MyNode) parent).onChildCheckChange(this, pre, now);
}
public void onChildCheckChange(Node child, boolean pre, boolean now) {
List<? extends Node> value = getItems().getValue();
if (value == null) {
return;
}
if (now && !isLeafParent()) {
for (Node node : value) {
if (node != child && node.isChecked().get()) {
node.setChecked(false);
}
}
}
}
@Override
public ObservableBoolean isChecked() {
return checked;
}
@Override
public void toggleChecked() {
setChecked(!checked.get());
}
}
然后创建我们的数据源:
public class DataSource {
private static LiveData<List> sData;
private static LiveData<Node> sKana;
private static LiveData<Node> sKana2;
public static LiveData<List> get() {
if (sData == null) {
sData = build();
}
return sData;
}
private static LiveData<List> build() {
LiveData<List> ret = new MutableLiveData<>();
List list = new ArrayList();
for (int i = 0; i < 20; i++) {
Group e = new Group("head" + i);
int num = 3 + ((int) (Math.random() * 7));
for (int j = 0; j < num; j++) {
e.addChild(new Item("child" + j));
}
list.add(e);
}
((MutableLiveData<List>) ret).setValue(list);
return ret;
}
public static LiveData<Node> getKanaNodes() {
if (sKana == null) {
sKana = buildKana();
}
return sKana;
}
public static LiveData<Node> getKanaNodes2() {
if (sKana2 == null) {
sKana2 = buildKanaOld();
}
return sKana2;
}
private static LiveData<Node> buildKana() {
LiveData<Node> ret = new MutableLiveData<>();
CityNode root = new CityNode(null, "root", 0, CityRepo.getInstance(App.sApp).getMap());
((MutableLiveData<Node>) ret).setValue(root);
return ret;
}
private static LiveData<Node> buildKanaOld() {
LiveData<Node> ret = new MutableLiveData<>();
((MutableLiveData<Node>) ret).setValue(new MyNode(null, "root", 0));
return ret;
}
}
然后再Activity中只需要简单的几行代码
KanaPresenterFactory factory = (mKanaView, lifecycleOwner, node, factory1) -> new KanaPresenter(mKanaView, lifecycleOwner, node, factory1);
DataSource.getKanaNodes2().observe(this, node -> {
binding.kana.setPresenter(factory.create(binding.kana, this, node, factory));
});
项目地址
其他
使用Databinding轻松快速打造仿携程app筛选控件(一)
使用Databinding轻松快速打造仿携程app筛选控件(二)
使用Databinding轻松快速打造仿携程app筛选控件(三)
more
Github | 简书 | 掘金 | JCenter | dockerHub |
---|---|---|---|---|
Github | 简书 | 掘金 | JCenter | dockerHub |