目录
UI视图
效果如下
以下是展示树型视图的xml文件和dialog界面
public class DataCollectCreateAreaDialog extends DataCollectBaseDialog {
private RelativeLayout rlArea;
@BindView(R.id.btn_data_collect_area_confirm)
Button btnConfirm;
private Map<String, Object> returnMap;
private List<Integer> addressIdList;
private List<String> saveNodeNameList;
private TreeNode root;
private TreeNode treeNode;
private TreeNode childTreeNode;
private TreeView treeView;
@SuppressLint("LongLogTag")
private void initView() {
rlArea = view.findViewById(R.id.rl_area);
root = TreeNode.root();
returnMap=new HashMap<>();
addressIdList = new ArrayList<>();
saveNodeNameList = new ArrayList<>();
AreaTreeCheckBoxUtil util = new AreaTreeCheckBoxUtil(context, rlArea);
((Toolbar) view.findViewById(R.id.toolbar)).setNavigationOnClickListener(v -> cancelOn());
btnConfirm.setOnClickListener(v1 -> {
saveNodeNameList.clear();
addressIdList.clear();
util.getData();
addressIdList = util.getSaveIdList();
saveNodeNameList = util.getSaveNodeNameList();
Log.d(TAG, " -=-=- initView: addressIdList:" + addressIdList);
Log.d(TAG, " -=-=- initView: saveNodeNameList:" + saveNodeNameList);
StringBuilder addressId = new StringBuilder();
StringBuilder sbName = new StringBuilder();
for (Integer integer : addressIdList) addressId.append(integer).append(";");
for (String s : saveNodeNameList) sbName.append(s).append(";");
Log.d(TAG, " -=-=- initView: sbName:" + sbName.toString());
if (TextUtils.isEmpty(sbName.toString())) showToast("请选择区域!");
else {
returnMap.put("addressName", sbName.subSequence(0, sbName.length() - 1));
if (!TextUtils.isEmpty(addressId.toString()))
returnMap.put("addressId", addressId.subSequence(0, addressId.length() - 1));
returnData();
cancelOn();
}
});
show();
}
...
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/new_broad_details_bg"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:title="选择区域"
app:titleTextColor="@color/c_ffffff"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_back_white_24" />
<RelativeLayout
android:id="@+id/rl_area"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_data_collect_area_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/c_4791ff"
android:text="@string/filtrateFinish"
android:textColor="@color/c_ffffff" />
</LinearLayout>
构造树型结构数据
AreaTreeCheckBoxUtil是构造树型结构数据的工具类,负责获取所有区域数据、构造树型结构数据
public class AreaTreeCheckBoxUtil {
private TreeNode root;
private TreeNode treeNode;
private TreeNode childTreeNode;
private TreeView treeView;
public List<Integer> saveIdList;
public List<String> saveNodeNameList;
private Map<Integer, Integer> hierarchyMap;
private Context context;
private RelativeLayout rlArea;
int nodeHierarchy;
public AreaTreeCheckBoxUtil(Context context, RelativeLayout rlArea) {
this.context = context;
this.rlArea = rlArea;
nodeHierarchy = 1;
initTree();
}
@SuppressLint("LongLogTag")
private void initTree() {
root = TreeNode.root();
saveIdList = new ArrayList<>(); // 保存区域id
saveNodeNameList = new ArrayList<>(); // 保存每个区域节点名称
hierarchyMap = new HashMap<>();
int id = SPUtils.getInstance().getInt(SpConfig.PermissionId);
String level = SPUtils.getInstance().getString(SpConfig.PermissionLevel);
// 请求所有区域的数据
DataCollectModel.roleAreaTree(id, level, (isSuccess,o) -> {
AreaEntity areaEntity = (AreaEntity) o;
Log.d("DataCollectCreateAreaDialog", " -=-=- initView: areaEntity:" + areaEntity);
System.out.println(" -=-=-=- initView: areaEntity:" + areaEntity);
if (areaEntity != null) {
initAreaData(areaEntity); // 构造树型结构数据
createNodeViewFactory(); // 将树型结构数据按父子等级存入map
initNodeView(); // 创建树型节点视图
}
});
}
private void initAreaData(AreaEntity areaEntity) {
for (AreaEntity.ReturnDataBean returnDatum : areaEntity.getReturnData()) {
treeNode = createTreeNode(returnDatum.getText(), returnDatum.getId(),
returnDatum.getParentId(), returnDatum.getAddressLevel());
root.addChild(treeNode);
if (!returnDatum.getChildren().isEmpty()) getChildArea(returnDatum, treeNode);
}
}
// 使用递归算法遍历每个子节点,将原始数据构造成树型数据结构
private void getChildArea(AreaEntity.ReturnDataBean child, TreeNode treeNode) {
for (AreaEntity.ReturnDataBean childBean : child.getChildren()) {
childTreeNode = createTreeNode(childBean.getText(), childBean.getId(),
childBean.getParentId(), childBean.getAddressLevel());
treeNode.addChild(childTreeNode);
if (!childBean.getChildren().isEmpty()) getChildArea(childBean, childTreeNode);
}
}
private TreeNode createTreeNode(String text, String addressId, String paranId, String addressLevel) {
TreeNode treeNode = new TreeNode(text);
treeNode.setAddressId(Integer.valueOf(addressId));
treeNode.setLevel(Integer.valueOf(paranId));
treeNode.setAddressLevel(addressLevel);
return treeNode;
}
private void initNodeView() {
treeView = new TreeView(root, context, new NodeViewFactory(context, hierarchyMap)
.setCallBack((expand, treeNode) -> {
Log.d("BroadCreateAreaDialog", " -=-=- onClick: id:" + treeNode.getAddressId());
Log.d("BroadCreateAreaDialog", " -=-=- onClick: level:" + treeNode.getLevel());
}));
View view = treeView.getView();
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
rlArea.addView(view);
}
private void createNodeViewFactory() {
for (TreeNode treeNode : root.getChildren()) {
hierarchyMap.put(treeNode.getLevel(), 1);
for (TreeNode treeNode1 : treeNode.getChildren()) {
hierarchyMap.put(treeNode1.getLevel(), 2);
for (TreeNode treeNode2 : treeNode1.getChildren()) {
hierarchyMap.put(treeNode2.getLevel(), 3);
for (TreeNode treeNode3 : treeNode2.getChildren()) {
hierarchyMap.put(treeNode3.getLevel(), 4);
for (TreeNode treeNode4 : treeNode3.getChildren()) {
hierarchyMap.put(treeNode4.getLevel(), 5);
}
}
}
}
}
}
// 获取当前选中的区域节点数据
public void getData() {
saveIdList.clear();
saveNodeNameList.clear();
for (TreeNode treeNode : root.getChildren()) {
if (treeNode.isSelected()) {
saveIdList.add(treeNode.getAddressId());
saveNodeNameList.add(treeNode.getValue() + "");
} else getData(treeNode);
}
}
private void getData(TreeNode node) {
for (TreeNode treeNode : node.getChildren()) {
if (treeNode.isSelected()) {
saveIdList.add(treeNode.getAddressId());
saveNodeNameList.add(treeNode.getValue() + "");
} else getData(treeNode);
}
}
public List<String> getSaveNodeNameList() {
getData();
return saveNodeNameList;
}
public List<Integer> getSaveIdList() {
getData();
return saveIdList;
}
}
节点视图
NodeViewFactory管理节点视图
public class NodeViewFactory extends BaseNodeViewFactory {
private static final String TAG = "NodeViewFactory";
private Context context;
private Map<Integer, Integer> hierarchyMap;
private NodeViewBinder.ClickCallBack callBack;
public NodeViewFactory setCallBack(NodeViewBinder.ClickCallBack callBack) {
this.callBack = callBack;
return this;
}
CheckBoxClickListener listener;
public interface CheckBoxClickListener {
void clickListener(TreeNode treeNode);
}
public NodeViewFactory setListener(CheckBoxClickListener listener) {
this.listener = listener;
return this;
}
public NodeViewFactory(Context context, Map<Integer, Integer> hierarchyMap) {
this.context = context;
this.hierarchyMap = hierarchyMap;
}
@Override
public BaseNodeViewBinder getNodeViewBinder(View view, int level) {
Log.d(TAG, " -=-=- getNodeViewBinder: level:" + level);
int hierarchy = hierarchyMap.get(level);
Log.d(TAG, " -=-=- getNodeViewBinder: level:" + level + " hierarchy:" + hierarchy);
float density = context.getResources().getDisplayMetrics().density;
int nodeHeight;
int nodeLeftPadding;
int leftPaddingDatum = 10;
nodeHeight = (int) (40 * density);
nodeLeftPadding = (int) (leftPaddingDatum * hierarchy * density);
return getBaseNodeViewBinder(view, nodeHeight, nodeLeftPadding, View.VISIBLE);
}
@Override
public void checkBoxClickListener(TreeNode treeNode) {
if (listener != null) listener.clickListener(treeNode);
}
private BaseNodeViewBinder getBaseNodeViewBinder(View view, int nodeHeight, int nodeLeftPadding, int visible) {
return new NodeViewBinder(context, view).setNodeBgColor(R.color.c_1B2F36)
.setNodeHeight(nodeHeight).setNodeLeftPadding(nodeLeftPadding)
.setNodeIconVisibility(visible).setCallBack(callBack).setNodeTextColor(R.color.c_ffffff);
}
}
NodeViewBinder节点视图的显示样式
public class NodeViewBinder extends CheckableNodeViewBinder {
private static final String TAG = "NodeViewBinder";
private Context context;
private TextView textView;
private ImageView imageView;
private RelativeLayout relativeLayout;
private LinearLayout linearLayout;
ClickCallBack callBack;
public interface ClickCallBack {
void onClick(boolean expand, TreeNode treeNode);
}
public NodeViewBinder setCallBack(ClickCallBack callBack) {
this.callBack = callBack;
return this;
}
public NodeViewBinder(Context context, View itemView) {
super(itemView);
this.context = context;
relativeLayout = itemView.findViewById(R.id.rl_node_root_layout);
linearLayout = itemView.findViewById(R.id.node_container);
imageView = itemView.findViewById(R.id.arrow_img);
textView = itemView.findViewById(R.id.node_name_view);
}
@Override
public int getCheckableViewId() {
return R.id.checkBox;
}
@Override
public int getLayoutId() {
return R.layout.node_item_level;
}
@Override
public void bindView(final TreeNode treeNode) {
// Log.d(TAG, " -=-=- bindView: treeNode:" + treeNode.getValue());
textView.setText(treeNode.getValue() + "");
imageView.setRotation(treeNode.isExpanded() ? 90 : 0);
imageView.setVisibility(treeNode.hasChild() ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void onNodeToggled(TreeNode treeNode, boolean expand) {
if (expand) {
imageView.animate().rotation(90).setDuration(200).start();
} else {
imageView.animate().rotation(0).setDuration(200).start();
}
callBack.onClick(expand, treeNode);
}
public NodeViewBinder setNodeIcon(int iconId) {
if (imageView != null) {
imageView.setImageResource(iconId);
}
return this;
}
public NodeViewBinder setNodeIconVisibility(int visibility) {
if (imageView != null) {
imageView.setVisibility(visibility);
}
return this;
}
public NodeViewBinder setNodeHeight(int dp) {
if (linearLayout != null) {
ViewGroup.LayoutParams layoutParams = linearLayout.getLayoutParams();
layoutParams.height = dp;
linearLayout.setLayoutParams(layoutParams);
}
return this;
}
public NodeViewBinder setNodeLeftPadding(int dp) {
if (linearLayout != null) {
linearLayout.setPadding(dp, 0, 0, 0);
}
return this;
}
public NodeViewBinder setNodeBgColor(int colorId) {
if (relativeLayout != null) {
relativeLayout.setBackgroundColor(context.getResources().getColor(colorId));
}
return this;
}
public NodeViewBinder setNodeTextColor(int colorId) {
if (relativeLayout != null) {
textView.setTextColor(context.getResources().getColor(colorId));
}
return this;
}
@Override
public void checkBoxClickListener(boolean expand, TreeNode treeNode) {
// if (callBack != null) callBack.onClick(expand,treeNode);
}
}
树视图
TreeView显示树型图
public class TreeView implements SelectableTreeAction {
private TreeNode root;
private Context context;
private BaseNodeViewFactory baseNodeViewFactory;
private RecyclerView rootView;
private TreeViewAdapter adapter;
private boolean itemSelectable = true;
public void setItemAnimator(RecyclerView.ItemAnimator itemAnimator) {
this.itemAnimator = itemAnimator;
if (rootView != null && itemAnimator != null) {
rootView.setItemAnimator(itemAnimator);
}
}
private RecyclerView.ItemAnimator itemAnimator;
public TreeView(@NonNull TreeNode root, @NonNull Context context, @NonNull BaseNodeViewFactory baseNodeViewFactory) {
this.root = root;
this.context = context;
this.baseNodeViewFactory = baseNodeViewFactory;
if (baseNodeViewFactory == null) {
throw new IllegalArgumentException("You must assign a BaseNodeViewFactory!");
}
}
public View getView() {
if (rootView == null) {
this.rootView = buildRootView();
}
return rootView;
}
@NonNull
private RecyclerView buildRootView() {
RecyclerView recyclerView = new RecyclerView(context);
/**
* disable multi touch event to prevent terrible data set error when calculate list.
*/
recyclerView.setMotionEventSplittingEnabled(false);
recyclerView.setItemAnimator(itemAnimator != null ? itemAnimator : new TreeItemAnimator());
SimpleItemAnimator itemAnimator = (SimpleItemAnimator) recyclerView.getItemAnimator();
itemAnimator.setSupportsChangeAnimations(false);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new TreeViewAdapter(context, root, baseNodeViewFactory);
adapter.setNoShowIconLevel(noShowIconLevel);
adapter.setTreeView(this);
recyclerView.setAdapter(adapter);
return recyclerView;
}
public void adapterNotify(){
adapter.notifyDataSetChanged();
}
@Override
public void expandAll() {
if (root == null) {
return;
}
TreeHelper.expandAll(root);
refreshTreeView();
}
public void refreshTreeView() {
if (rootView != null) {
((TreeViewAdapter) rootView.getAdapter()).refreshView();
}
}
@Override
public void expandNode(TreeNode treeNode) {
adapter.expandNode(treeNode);
}
@Override
public void expandLevel(int level) {
TreeHelper.expandLevel(root, level);
refreshTreeView();
}
@Override
public void collapseAll() {
if (root == null) {
return;
}
TreeHelper.collapseAll(root);
refreshTreeView();
}
@Override
public void collapseNode(TreeNode treeNode) {
adapter.collapseNode(treeNode);
}
@Override
public void collapseLevel(int level) {
TreeHelper.collapseLevel(root, level);
refreshTreeView();
}
@Override
public void toggleNode(TreeNode treeNode) {
if (treeNode.isExpanded()) {
collapseNode(treeNode);
} else {
expandNode(treeNode);
}
}
@Override
public void deleteNode(TreeNode node) {
adapter.deleteNode(node);
}
@Override
public void addNode(TreeNode parent, TreeNode treeNode) {
parent.addChild(treeNode);
refreshTreeView();
}
@Override
public List<TreeNode> getAllNodes() {
return TreeHelper.getAllNodes(root);
}
@Override
public void selectNode(TreeNode treeNode) {
if (treeNode != null) {
Log.d("TreeView", " -=-=- selectNode: treeNode:" + treeNode);
adapter.selectNode(true, treeNode);
}
}
@Override
public void deselectNode(TreeNode treeNode) {
if (treeNode != null) {
adapter.selectNode(false, treeNode);
}
}
@Override
public void selectAll() {
TreeHelper.selectNodeAndChild(root, true);
refreshTreeView();
}
@Override
public void deselectAll() {
TreeHelper.selectNodeAndChild(root, false);
refreshTreeView();
}
@Override
public List<TreeNode> getSelectedNodes() {
return TreeHelper.getSelectedNodes(root);
}
public boolean isItemSelectable() {
return itemSelectable;
}
public void setItemSelectable(boolean itemSelectable) {
this.itemSelectable = itemSelectable;
}
private int[] noShowIconLevel;
public TreeView setNoShowIconLevel(int[] noShowIconLevel) {
this.noShowIconLevel = noShowIconLevel;
return this;
}
}
TreeItemAnimator树节点动画效果
public class TreeItemAnimator extends DefaultItemAnimator {
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
super.animateAdd(holder);
ViewCompat.setAlpha(holder.itemView, 1);
return true;
}
}
TreeHelper负责节点管理
比如展开所有树型节点、选中某个父及其下子节点等。
public class TreeHelper {
public static void expandAll(TreeNode node) {
if (node == null) {
return;
}
expandNode(node, true);
}
/**
* Expand node and calculate the visible addition nodes.
*
* @param treeNode target node to expand
* @param includeChild should expand child
* @return the visible addition nodes
*/
public static List<TreeNode> expandNode(TreeNode treeNode, boolean includeChild) {
List<TreeNode> expandChildren = new ArrayList<>();
if (treeNode == null) {
return expandChildren;
}
treeNode.setExpanded(true);
if (!treeNode.hasChild()) {
return expandChildren;
}
for (TreeNode child : treeNode.getChildren()) {
expandChildren.add(child);
if (includeChild || child.isExpanded()) {
expandChildren.addAll(expandNode(child, includeChild));
}
}
return expandChildren;
}
/**
* Expand the same deep(level) nodes.
*
* @param root the tree root
* @param level the level to expand
* @return
*/
public static void expandLevel(TreeNode root, int level) {
if (root == null) {
return;
}
for (TreeNode child : root.getChildren()) {
if (child.getLevel() == level) {
expandNode(child, false);
} else {
expandLevel(child, level);
}
}
}
public static void collapseAll(TreeNode node) {
if (node == null) {
return;
}
for (TreeNode child : node.getChildren()) {
performCollapseNode(child, true);
}
}
/**
* Collapse node and calculate the visible removed nodes.
*
* @param node target node to collapse
* @param includeChild should collapse child
* @return the visible addition nodes before remove
*/
public static List<TreeNode> collapseNode(TreeNode node, boolean includeChild) {
List<TreeNode> treeNodes = performCollapseNode(node, includeChild);
node.setExpanded(false);
return treeNodes;
}
private static List<TreeNode> performCollapseNode(TreeNode node, boolean includeChild) {
List<TreeNode> collapseChildren = new ArrayList<>();
if (node == null) {
return collapseChildren;
}
if (includeChild) {
node.setExpanded(false);
}
for (TreeNode child : node.getChildren()) {
collapseChildren.add(child);
if (child.isExpanded()) {
collapseChildren.addAll(performCollapseNode(child, includeChild));
} else if (includeChild) {
performCollapseNodeInner(child);
}
}
return collapseChildren;
}
/**
* Collapse all children node recursive
*
* @param node target node to collapse
*/
private static void performCollapseNodeInner(TreeNode node) {
if (node == null) {
return;
}
node.setExpanded(false);
for (TreeNode child : node.getChildren()) {
performCollapseNodeInner(child);
}
}
public static void collapseLevel(TreeNode root, int level) {
if (root == null) {
return;
}
for (TreeNode child : root.getChildren()) {
if (child.getLevel() == level) {
collapseNode(child, false);
} else {
collapseLevel(child, level);
}
}
}
public static List<TreeNode> getAllNodes(TreeNode root) {
List<TreeNode> allNodes = new ArrayList<>();
fillNodeList(allNodes, root);
allNodes.remove(root);
return allNodes;
}
private static void fillNodeList(List<TreeNode> treeNodes, TreeNode treeNode) {
treeNodes.add(treeNode);
if (treeNode.hasChild()) {
for (TreeNode child : treeNode.getChildren()) {
fillNodeList(treeNodes, child);
}
}
}
/**
* Select the node and node's children,return the visible nodes
*
* @param treeNode
* @param select
* @return
*/
public static List<TreeNode> selectNodeAndChild(TreeNode treeNode, boolean select) {
List<TreeNode> expandChildren = new ArrayList<>();
if (treeNode == null) {
return expandChildren;
}
treeNode.setSelected(select);
if (!treeNode.hasChild()) {
return expandChildren;
}
if (treeNode.isExpanded()) {
for (TreeNode child : treeNode.getChildren()) {
expandChildren.add(child);
if (child.isExpanded()) {
expandChildren.addAll(selectNodeAndChild(child, select));
} else {
selectNodeInner(child, select);
}
}
} else {
selectNodeInner(treeNode, select);
}
return expandChildren;
}
private static void selectNodeInner(TreeNode treeNode, boolean select) {
if (treeNode == null) {
return;
}
treeNode.setSelected(select);
if (treeNode.hasChild()) {
for (TreeNode child : treeNode.getChildren()) {
selectNodeInner(child, select);
}
}
}
/**
* Select parent when all the brothers have been selected, otherwise deselect parent,
* and check the grand parent recursive.
*
* @param treeNode
* @param select
* @return
*/
public static List<TreeNode> selectParentIfNeedWhenNodeSelected(TreeNode treeNode, boolean select) {
List<TreeNode> impactedParents = new ArrayList<>();
if (treeNode == null) {
return impactedParents;
}
//ensure that the node's level is bigger than 1(first level is 1)
TreeNode parent = treeNode.getParent();
if (parent == null || parent.getParent() == null) {
return impactedParents;
}
List<TreeNode> brothers = parent.getChildren();
int selectedBrotherCount = 0;
for (TreeNode brother : brothers) {
if (brother.isSelected()) selectedBrotherCount++;
}
if (select && selectedBrotherCount == brothers.size()) {
parent.setSelected(true);
impactedParents.add(parent);
impactedParents.addAll(selectParentIfNeedWhenNodeSelected(parent, true));
} else if (!select && selectedBrotherCount == brothers.size() - 1) {
// only the condition that the size of selected's brothers
// is one less than total count can trigger the deselect
parent.setSelected(false);
impactedParents.add(parent);
impactedParents.addAll(selectParentIfNeedWhenNodeSelected(parent, false));
}
return impactedParents;
}
/**
* Get the selected nodes under current node, include itself
*
* @param treeNode
* @return
*/
public static List<TreeNode> getSelectedNodes(TreeNode treeNode) {
List<TreeNode> selectedNodes = new ArrayList<>();
if (treeNode == null) {
return selectedNodes;
}
if (treeNode.isSelected() && treeNode.getParent() != null) selectedNodes.add(treeNode);
for (TreeNode child : treeNode.getChildren()) {
selectedNodes.addAll(getSelectedNodes(child));
}
return selectedNodes;
}
/**
* Return true when the node has one selected child(recurse all children) at least,
* otherwise return false
*
* @param treeNode
* @return
*/
public static boolean hasOneSelectedNodeAtLeast(TreeNode treeNode) {
if (treeNode == null || treeNode.getChildren().size() == 0) {
return false;
}
List<TreeNode> children = treeNode.getChildren();
for (TreeNode child : children) {
if (child.isSelected() || hasOneSelectedNodeAtLeast(child)) {
return true;
}
}
return false;
}
}