add show node tree

This commit is contained in:
王劲鹏 2021-07-16 18:11:57 +08:00 committed by osborn
parent 502d731489
commit 46eff0d904
11 changed files with 671 additions and 1 deletions

View File

@ -11,5 +11,6 @@
android:name=".ui.DoricDevActivity" android:name=".ui.DoricDevActivity"
android:theme="@style/Theme.Design.Light.NoActionBar" /> android:theme="@style/Theme.Design.Light.NoActionBar" />
<activity android:name=".qrcode.activity.CaptureActivity" /> <activity android:name=".qrcode.activity.CaptureActivity" />
<activity android:name=".ui.DoricShowNodeTreeActivity" />
</application> </application>
</manifest> </manifest>

View File

@ -21,7 +21,6 @@ import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -50,6 +49,8 @@ import pub.doric.devkit.qrcode.DisplayUtil;
import pub.doric.devkit.qrcode.activity.CaptureActivity; import pub.doric.devkit.qrcode.activity.CaptureActivity;
import pub.doric.devkit.qrcode.activity.CodeUtils; import pub.doric.devkit.qrcode.activity.CodeUtils;
import static pub.doric.devkit.ui.DoricShowNodeTreeActivity.DORIC_CONTEXT_ID_KEY;
public class DoricDevActivity extends AppCompatActivity implements DoricDev.StatusCallback { public class DoricDevActivity extends AppCompatActivity implements DoricDev.StatusCallback {
private int REQUEST_CODE = 100; private int REQUEST_CODE = 100;
private ContextCellAdapter cellAdapter; private ContextCellAdapter cellAdapter;
@ -363,6 +364,9 @@ public class DoricDevActivity extends AppCompatActivity implements DoricDev.Stat
builder.show(); builder.show();
} }
}); });
ArrayList<String> list = new ArrayList<>();
list.add("View source");
list.add("Show node tree");
if (DoricDev.getInstance().isInDevMode()) { if (DoricDev.getInstance().isInDevMode()) {
if (context.getDriver() instanceof DoricDebugDriver) { if (context.getDriver() instanceof DoricDebugDriver) {
actionMap.put("Stop debugging", new DialogInterface.OnClickListener() { actionMap.put("Stop debugging", new DialogInterface.OnClickListener() {

View File

@ -0,0 +1,100 @@
package pub.doric.devkit.ui;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import pub.doric.DoricContext;
import pub.doric.DoricContextManager;
import pub.doric.devkit.R;
import pub.doric.devkit.ui.treeview.DoricViewNodeLayoutItemType;
import pub.doric.devkit.ui.treeview.DoricViewNodeTreeViewBinder;
import pub.doric.devkit.ui.treeview.TreeNode;
import pub.doric.devkit.ui.treeview.TreeViewAdapter;
import pub.doric.shader.GroupNode;
import pub.doric.shader.ViewNode;
public class DoricShowNodeTreeActivity extends AppCompatActivity {
public static final String DORIC_CONTEXT_ID_KEY = "DORIC_CONTEXT_ID";
private RecyclerView rv;
private TreeViewAdapter adapter;
private DoricContext doricContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String contextId = getIntent().getStringExtra(DORIC_CONTEXT_ID_KEY);
doricContext = DoricContextManager.getContext(contextId);
setContentView(R.layout.layout_show_node_tree);
initView();
initData();
}
private void initData() {
List<TreeNode> nodes = new ArrayList<>();
TreeNode<DoricViewNodeLayoutItemType> root = new TreeNode<>(new DoricViewNodeLayoutItemType(doricContext.getRootNode()));
Queue<ViewNode> viewQueue = new LinkedList<>();
Queue<TreeNode> treeQueue = new LinkedList<>();
viewQueue.offer(doricContext.getRootNode());
treeQueue.offer(root);
while (!viewQueue.isEmpty()) {
ViewNode viewNode = viewQueue.poll();
TreeNode treeNode = treeQueue.poll();
if (viewNode instanceof GroupNode) {
GroupNode groupNode = (GroupNode) viewNode;
for (int i = 0; i != groupNode.getChildNodes().size(); i++) {
assert treeNode != null;
TreeNode<DoricViewNodeLayoutItemType> temp = new TreeNode(new DoricViewNodeLayoutItemType((ViewNode) groupNode.getChildNodes().get(i)));
treeNode.addChild(temp);
viewQueue.offer((ViewNode) groupNode.getChildNodes().get(i));
treeQueue.offer(temp);
}
}
}
nodes.add(root);
rv.setLayoutManager(new LinearLayoutManager(this));
adapter = new TreeViewAdapter(nodes, Collections.singletonList(new DoricViewNodeTreeViewBinder()));
// whether collapse child nodes when their parent node was close.
// adapter.ifCollapseChildWhileCollapseParent(true);
adapter.setOnTreeNodeListener(new TreeViewAdapter.OnTreeNodeListener() {
@Override
public boolean onClick(TreeNode node, RecyclerView.ViewHolder holder) {
if (!node.isLeaf()) {
//Update and toggle the node.
onToggle(!node.isExpand(), holder);
// if (!node.isExpand())
// adapter.collapseBrotherNode(node);
}
return false;
}
@Override
public void onToggle(boolean isExpand, RecyclerView.ViewHolder holder) {
DoricViewNodeTreeViewBinder.ViewHolder viewNodeTreeViewBinder = (DoricViewNodeTreeViewBinder.ViewHolder) holder;
}
});
rv.setAdapter(adapter);
}
private void initView() {
rv = findViewById(R.id.show_node_tree_rv);
}
}

View File

@ -0,0 +1,17 @@
package pub.doric.devkit.ui.treeview;
import pub.doric.devkit.R;
import pub.doric.shader.ViewNode;
public class DoricViewNodeLayoutItemType implements LayoutItemType {
private final ViewNode viewNode;
public DoricViewNodeLayoutItemType(ViewNode viewNode) {
this.viewNode = viewNode;
}
@Override
public int getLayoutId() {
return R.layout.layout_show_node_tree_cell;
}
}

View File

@ -0,0 +1,30 @@
package pub.doric.devkit.ui.treeview;
import android.view.View;
import pub.doric.devkit.R;
public class DoricViewNodeTreeViewBinder extends TreeViewBinder<DoricViewNodeTreeViewBinder.ViewHolder> {
@Override
public ViewHolder provideViewHolder(View itemView) {
return new ViewHolder(itemView);
}
@Override
public void bindView(ViewHolder holder, int position, TreeNode node) {
DoricViewNodeLayoutItemType layoutItemType = (DoricViewNodeLayoutItemType) node.getContent();
}
@Override
public int getLayoutId() {
return R.layout.layout_show_node_tree_cell;
}
public static class ViewHolder extends TreeViewBinder.ViewHolder {
public ViewHolder(View rootView) {
super(rootView);
}
}
}

View File

@ -0,0 +1,5 @@
package pub.doric.devkit.ui.treeview;
public interface LayoutItemType {
int getLayoutId();
}

View File

@ -0,0 +1,145 @@
package pub.doric.devkit.ui.treeview;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
public class TreeNode<T extends LayoutItemType> implements Cloneable {
private T content;
private TreeNode parent;
private List<TreeNode> childList;
private boolean isExpand;
private boolean isLocked;
//the tree high
private int height = UNDEFINE;
private static final int UNDEFINE = -1;
public TreeNode(@NonNull T content) {
this.content = content;
this.childList = new ArrayList<>();
}
public int getHeight() {
if (isRoot())
height = 0;
else if (height == UNDEFINE)
height = parent.getHeight() + 1;
return height;
}
public boolean isRoot() {
return parent == null;
}
public boolean isLeaf() {
return childList == null || childList.isEmpty();
}
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public List<TreeNode> getChildList() {
return childList;
}
public void setChildList(List<TreeNode> childList) {
this.childList.clear();
for (TreeNode treeNode : childList) {
addChild(treeNode);
}
}
public TreeNode addChild(TreeNode node) {
if (childList == null)
childList = new ArrayList<>();
childList.add(node);
node.parent = this;
return this;
}
public boolean toggle() {
isExpand = !isExpand;
return isExpand;
}
public void collapse() {
if (isExpand) {
isExpand = false;
}
}
public void collapseAll() {
if (childList == null || childList.isEmpty()) {
return;
}
for (TreeNode child : this.childList) {
child.collapseAll();
}
}
public void expand() {
if (!isExpand) {
isExpand = true;
}
}
public void expandAll() {
expand();
if (childList == null || childList.isEmpty()) {
return;
}
for (TreeNode child : this.childList) {
child.expandAll();
}
}
public boolean isExpand() {
return isExpand;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
public TreeNode getParent() {
return parent;
}
public TreeNode<T> lock() {
isLocked = true;
return this;
}
public TreeNode<T> unlock() {
isLocked = false;
return this;
}
public boolean isLocked() {
return isLocked;
}
@Override
public String toString() {
return "TreeNode{" +
"content=" + this.content +
", parent=" + (parent == null ? "null" : parent.getContent().toString()) +
", childList=" + (childList == null ? "null" : childList.toString()) +
", isExpand=" + isExpand +
'}';
}
@Override
protected TreeNode<T> clone() throws CloneNotSupportedException {
TreeNode<T> clone = new TreeNode<>(this.content);
clone.isExpand = this.isExpand;
return clone;
}
}

View File

@ -0,0 +1,325 @@
package pub.doric.devkit.ui.treeview;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TreeViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String KEY_IS_EXPAND = "IS_EXPAND";
private final List<? extends TreeViewBinder> viewBinders;
private List<TreeNode> displayNodes;
private int padding = 30;
private OnTreeNodeListener onTreeNodeListener;
private boolean toCollapseChild;
public TreeViewAdapter(List<? extends TreeViewBinder> viewBinders) {
this(null, viewBinders);
}
public TreeViewAdapter(List<TreeNode> nodes, List<? extends TreeViewBinder> viewBinders) {
displayNodes = new ArrayList<>();
if (nodes != null)
findDisplayNodes(nodes);
this.viewBinders = viewBinders;
}
/**
* 从nodes的结点中寻找展开了的非叶结点添加到displayNodes中
*
* @param nodes 基准点
*/
private void findDisplayNodes(List<TreeNode> nodes) {
for (TreeNode node : nodes) {
displayNodes.add(node);
if (!node.isLeaf() && node.isExpand())
findDisplayNodes(node.getChildList());
}
}
@Override
public int getItemViewType(int position) {
return displayNodes.get(position).getContent().getLayoutId();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(viewType, parent, false);
if (viewBinders.size() == 1)
return viewBinders.get(0).provideViewHolder(v);
for (TreeViewBinder viewBinder : viewBinders) {
if (viewBinder.getLayoutId() == viewType)
return viewBinder.provideViewHolder(v);
}
return viewBinders.get(0).provideViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
if (payloads != null && !payloads.isEmpty()) {
Bundle b = (Bundle) payloads.get(0);
for (String key : b.keySet()) {
switch (key) {
case KEY_IS_EXPAND:
if (onTreeNodeListener != null)
onTreeNodeListener.onToggle(b.getBoolean(key), holder);
break;
}
}
}
super.onBindViewHolder(holder, position, payloads);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
holder.itemView.setPaddingRelative(displayNodes.get(position).getHeight() * padding, 3, 3, 3);
} else {
holder.itemView.setPadding(displayNodes.get(position).getHeight() * padding, 3, 3, 3);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TreeNode selectedNode = displayNodes.get(holder.getLayoutPosition());
// Prevent multi-click during the short interval.
try {
long lastClickTime = (long) holder.itemView.getTag();
if (System.currentTimeMillis() - lastClickTime < 500)
return;
} catch (Exception e) {
holder.itemView.setTag(System.currentTimeMillis());
}
holder.itemView.setTag(System.currentTimeMillis());
if (onTreeNodeListener != null && onTreeNodeListener.onClick(selectedNode, holder))
return;
if (selectedNode.isLeaf())
return;
// This TreeNode was locked to click.
if (selectedNode.isLocked()) return;
boolean isExpand = selectedNode.isExpand();
int positionStart = displayNodes.indexOf(selectedNode) + 1;
if (!isExpand) {
notifyItemRangeInserted(positionStart, addChildNodes(selectedNode, positionStart));
} else {
notifyItemRangeRemoved(positionStart, removeChildNodes(selectedNode, true));
}
}
});
for (TreeViewBinder viewBinder : viewBinders) {
if (viewBinder.getLayoutId() == displayNodes.get(position).getContent().getLayoutId())
viewBinder.bindView(holder, position, displayNodes.get(position));
}
}
private int addChildNodes(TreeNode pNode, int startIndex) {
List<TreeNode> childList = pNode.getChildList();
int addChildCount = 0;
for (TreeNode treeNode : childList) {
displayNodes.add(startIndex + addChildCount++, treeNode);
if (treeNode.isExpand()) {
addChildCount += addChildNodes(treeNode, startIndex + addChildCount);
}
}
if (!pNode.isExpand())
pNode.toggle();
return addChildCount;
}
private int removeChildNodes(TreeNode pNode) {
return removeChildNodes(pNode, true);
}
private int removeChildNodes(TreeNode pNode, boolean shouldToggle) {
if (pNode.isLeaf())
return 0;
List<TreeNode> childList = pNode.getChildList();
int removeChildCount = childList.size();
displayNodes.removeAll(childList);
for (TreeNode child : childList) {
if (child.isExpand()) {
if (toCollapseChild)
child.toggle();
removeChildCount += removeChildNodes(child, false);
}
}
if (shouldToggle)
pNode.toggle();
return removeChildCount;
}
@Override
public int getItemCount() {
return displayNodes == null ? 0 : displayNodes.size();
}
public void setPadding(int padding) {
this.padding = padding;
}
public void ifCollapseChildWhileCollapseParent(boolean toCollapseChild) {
this.toCollapseChild = toCollapseChild;
}
public void setOnTreeNodeListener(OnTreeNodeListener onTreeNodeListener) {
this.onTreeNodeListener = onTreeNodeListener;
}
public interface OnTreeNodeListener {
/**
* called when TreeNodes were clicked.
*
* @return weather consume the click event.
*/
boolean onClick(TreeNode node, RecyclerView.ViewHolder holder);
/**
* called when TreeNodes were toggle.
*
* @param isExpand the status of TreeNodes after being toggled.
*/
void onToggle(boolean isExpand, RecyclerView.ViewHolder holder);
}
public void refresh(List<TreeNode> treeNodes) {
displayNodes.clear();
findDisplayNodes(treeNodes);
notifyDataSetChanged();
}
public Iterator<TreeNode> getDisplayNodesIterator() {
return displayNodes.iterator();
}
private void notifyDiff(final List<TreeNode> temp) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return temp.size();
}
@Override
public int getNewListSize() {
return displayNodes.size();
}
// judge if the same items
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.areItemsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
// if they are the same items, whether the contents has bean changed.
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.areContentsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.getChangePayload(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
});
diffResult.dispatchUpdatesTo(this);
}
private Object getChangePayload(TreeNode oldNode, TreeNode newNode) {
Bundle diffBundle = new Bundle();
if (newNode.isExpand() != oldNode.isExpand()) {
diffBundle.putBoolean(KEY_IS_EXPAND, newNode.isExpand());
}
if (diffBundle.size() == 0)
return null;
return diffBundle;
}
// For DiffUtil, if they are the same items, whether the contents has bean changed.
private boolean areContentsTheSame(TreeNode oldNode, TreeNode newNode) {
return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent())
&& oldNode.isExpand() == newNode.isExpand();
}
// judge if the same item for DiffUtil
private boolean areItemsTheSame(TreeNode oldNode, TreeNode newNode) {
return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent());
}
/**
* collapse all root nodes.
*/
public void collapseAll() {
// Back up the nodes are displaying.
List<TreeNode> temp = backupDisplayNodes();
//find all root nodes.
List<TreeNode> roots = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
if (displayNode.isRoot())
roots.add(displayNode);
}
//Close all root nodes.
for (TreeNode root : roots) {
if (root.isExpand())
removeChildNodes(root);
}
notifyDiff(temp);
}
@NonNull
private List<TreeNode> backupDisplayNodes() {
List<TreeNode> temp = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
try {
temp.add(displayNode.clone());
} catch (CloneNotSupportedException e) {
temp.add(displayNode);
}
}
return temp;
}
public void collapseNode(TreeNode pNode) {
List<TreeNode> temp = backupDisplayNodes();
removeChildNodes(pNode);
notifyDiff(temp);
}
public void collapseBrotherNode(TreeNode pNode) {
List<TreeNode> temp = backupDisplayNodes();
if (pNode.isRoot()) {
List<TreeNode> roots = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
if (displayNode.isRoot())
roots.add(displayNode);
}
//Close all root nodes.
for (TreeNode root : roots) {
if (root.isExpand() && !root.equals(pNode))
removeChildNodes(root);
}
} else {
TreeNode parent = pNode.getParent();
if (parent == null)
return;
List<TreeNode> childList = parent.getChildList();
for (TreeNode node : childList) {
if (node.equals(pNode) || !node.isExpand())
continue;
removeChildNodes(node);
}
}
notifyDiff(temp);
}
}

View File

@ -0,0 +1,23 @@
package pub.doric.devkit.ui.treeview;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.recyclerview.widget.RecyclerView;
public abstract class TreeViewBinder<VH extends RecyclerView.ViewHolder> implements LayoutItemType {
public abstract VH provideViewHolder(View itemView);
public abstract void bindView(VH holder, int position, TreeNode node);
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View rootView) {
super(rootView);
}
protected <T extends View> T findViewById(@IdRes int id) {
return itemView.findViewById(id);
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/show_node_tree_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/node_type_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>