上一篇我們介紹了只用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));
});
複製程式碼
專案地址
more
Github | 簡書 | 掘金 | JCenter | dockerHub |
---|---|---|---|---|
Github | 簡書 | 掘金 | JCenter | dockerHub |