1.开发原因
在项目中经常需要一个需要一个树状框架,这是非常常见的控件。不过可能是谷歌考虑到android是手机系统,界面宽度有限,
所以只提供了只有二级的ExpandableListView。虽然这个控件可以满足很多需求,但是无数级的树在某些情况下还是需要的,所以我花了一天时间
(大部分时间都在调试动画去了,不过现在动画还有点问题,具体原因不明。。如果某位大神能找到原因灰常感谢)。
注:今早起来终于修复了最后一个bug,现在的动画效果已经非常完美了,等下就把加了注释的代码贴上来。
2.原理
网上很多都是扩展listview实现的,不过listview貌似不支持复杂控件的事件?而且做动画也不方便,所有我决定扩展linearlayout,在里面增加子节点的方式实现。
3.上图
图片是gif,真实效果要更加流畅一些,但是bug也还在的,就是开始隐藏的节点无法获取高度,可以通过自己事先设置解决。
3.代码
TreeView.java:


1 package net.memornote.android.ui.view;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.List;
6 import java.util.Timer;
7 import java.util.TimerTask;
8
9 import android.content.Context;
10 import android.graphics.Rect;
11 import android.util.AttributeSet;
12 import android.view.View;
13 import android.view.animation.Animation;
14 import android.view.animation.Animation.AnimationListener;
15 import android.view.animation.ScaleAnimation;
16 import android.view.animation.TranslateAnimation;
17 import android.widget.LinearLayout;
18
19 public class TreeView extends LinearLayout{
20
21 // private List<TreeItem> items;
22 private List<TreeItem> sortedItems;
23 private int animTime;
24
25 public TreeView(Context context, AttributeSet attrs) {
26 super(context, attrs);
27 setOrientation(LinearLayout.VERTICAL);
28 }
29 /**
30 * initialize data,you must make sure that each item has parent except the top ones.
31 * @param items the data to show
32 * @param index the index of the tree to insert to
33 * @param viewHeight each view's height
34 * @param animTime if you want expand animation,
35 * you can set the time(ms) of animation,otherwise you can set 0.
36 *
37 */
38 public void initData(Collection<TreeItem> items,int index){
39
40 if(items==null||items.size()==0){
41 return ;
42 }
43
44 sortItemList(items);
45
46 int size=sortedItems.size();
47
48 initAddIndex=index<0?-Integer.MAX_VALUE:index;
49
50 for (int i=0;i<size;i++) {
51 TreeItem item=sortedItems.get(i);
52 recuseShow(item);
53 }
54
55 }
56
57 private boolean isAnim=false;
58 /**
59 * 这个方法还有很 严重的bug,无法使用。。
60 * 设置为0则关闭动画
61 * @param animTime
62 */
63 public void enabledAnim(int animTime) {
64 if(animTime<0){
65 isAnim=false;
66 return ;
67 }
68 this.animTime=animTime;
69 isAnim=true;
70 }
71
72 private int initAddIndex;
73
74 private void recuseShow(TreeItem item){
75 View view=item.getView();
76 addView(view,initAddIndex);
77 if(item.getParent()!=null){
78 view.setVisibility(View.GONE);
79 }else {
80 view.setVisibility(View.VISIBLE);
81 }
82 initAddIndex++;
83 List<TreeItem> childrens=item.getChildrens();
84 if(childrens.size()>0){
85 for (TreeItem it : childrens) {
86 recuseShow(it);
87 }
88 }
89 }
90
91 private void sortItemList(Collection<TreeItem> items) {
92 //把items按照层级关系存放,sortedItems只存放顶层的item
93 sortedItems=new ArrayList<TreeItem>(5);
94 for (TreeItem item : items) {
95 if(item.getParent()==null){
96 sortedItems.add(item);
97 }else {
98 item.getParent().getChildrens().add(item);
99 }
100 }
101
102 }
103
104
105 private int viewIndex=0;
106 private int animHeight=0;
107 private void addChild(TreeItem item,boolean isRecurse){
108 if(item.getChildrens().size()>0){
109 List<TreeItem> list=item.getChildrens();
110 for (TreeItem it : list) {
111 View view=it.getView();
112 if(view.isShown()){
113 continue;
114 }
115 viewIndex++;
116 view.setVisibility(View.VISIBLE);
117 if(isAnim){
118 animHeight-=it.getViewHeight();
119 }
120 it.nextIsExpand=true;
121 if(isRecurse){
122 addChild(it,true);
123 }
124 }
125 }
126 }
127 private int removeCount=0;
128 private synchronized void removeChild(TreeItem item,boolean isRecurse){
129 if(item.getChildrens().size()>0){
130 List<TreeItem> list=item.getChildrens();
131 for (TreeItem it : list) {
132 View view=it.getView();
133 if(!view.isShown()){
134 continue;
135 }
136
137 TranslateAnimation ta=new TranslateAnimation(0, 0, 0, 0);
138 ta.setFillBefore(true);
139 ta.setDuration(1000);
140 view.startAnimation(ta);
141 removeCount++;
142 view.setVisibility(View.GONE);
143 if(isAnim){
144 animHeight+=it.getViewHeight();
145 }
146 if(isRecurse){
147 removeChild(it,true);
148 }
149 }
150 }
151 }
152
153 private void animAdd(){
154 TranslateAnimation ta=new TranslateAnimation(
155 Animation.ABSOLUTE, 0,
156 Animation.ABSOLUTE, 0,
157 Animation.ABSOLUTE, animHeight,
158 Animation.ABSOLUTE, 0);
159 ta.setFillBefore(true);
160 ta.setFillAfter(false);
161 ta.setDuration(animTime);
162
163 for (int i = viewIndex+1; i < getChildCount(); i++) {
164 View view=getChildAt(i);
165 if(view.isShown()){
166 view.startAnimation(ta);
167 }
168 }
169 animHeight=0;
170 }
171 private void animRemove(){
172 TranslateAnimation ta=new TranslateAnimation(
173 Animation.ABSOLUTE, 0,
174 Animation.ABSOLUTE, 0,
175 Animation.ABSOLUTE, animHeight,
176 Animation.ABSOLUTE, 0);
177 ta.setFillAfter(false);
178 ta.setFillBefore(true);
179 ta.setDuration(animTime);
180
181 int startAnimIndex;
182 startAnimIndex=viewIndex+1;
183 for (int i = startAnimIndex; i < getChildCount(); i++) {
184 View view=getChildAt(i);
185 if(view.isShown()){
186 view.startAnimation(ta);
187 }
188 }
189 animHeight=0;
190 }
191 public void expand(TreeItem item){
192 viewIndex=indexOfChild(item.getView());
193 addChild(item,false);
194 if(isAnim){
195 animAdd();
196 }
197 }
198
199
200 public void expandAllChildren(TreeItem item) {
201 viewIndex=indexOfChild(item.getView());
202 addChild(item,true);
203 if(isAnim){
204 animAdd();
205 }
206 }
207
208 public void expandAll(){
209 if(sortedItems==null){
210 return ;
211 }
212 for (TreeItem item : sortedItems) {
213 expandAllChildren(item);
214 }
215 }
216
217 public void contractAllChildren(TreeItem item) {
218 viewIndex=indexOfChild(item.getView())+1;
219 removeChild(item,true);
220 if(isAnim){
221 animRemove();
222 }
223 }
224
225 public void contractAll(){
226 if(sortedItems==null){
227 return ;
228 }
229 for (TreeItem item : sortedItems) {
230 contractAllChildren(item);
231 }
232 }
233
234 public void bind(TreeItem item) {
235 if(item.nextIsExpand){
236 expand(item);
237 }else {
238 contractAllChildren(item);
239 }
240 item.nextIsExpand=!item.nextIsExpand;
241 }
242
243
244 }
TreeItem.java:


package net.memornote.android.ui.view;
import java.util.ArrayList;
import java.util.List;
import android.view.View;
public class TreeItem {
private View view;
private TreeItem parent;
private List<TreeItem> childrens=new ArrayList<TreeItem>(0);
public boolean nextIsExpand=true;
private int viewHeight;
public TreeItem(){}
/**
* 初始化TreeItem
* @param view
* @param parent
*/
public TreeItem(View view, TreeItem parent) {
super();
this.view = view;
this.parent = parent;
}
/**
* 初始化TreeItem
* @param view
* @param parent
*/
public TreeItem(View view, TreeItem parent,int viewHeight) {
super();
this.view = view;
this.parent = parent;
this.viewHeight=viewHeight;
}
public View getView() {
if(view!=null){
view.setPadding(getLevel()*20,0,0,0);
}
return view;
}
public void setView(View view) {
this.view = view;
}
public TreeItem getParent() {
return parent;
}
public void setParent(TreeItem parent) {
this.parent = parent;
}
/**
* 动态获取该节点的级数
* @return
*/
public int getLevel() {
int level=0;
TreeItem localParent=parent;
while (localParent!=null) {
level++;
localParent=localParent.getParent();
}
return level;
}
public List<TreeItem> getChildrens() {
return childrens;
}
public int getViewHeight() {
if(view==null||view.getHeight()==0){
return viewHeight;
}
return view.getHeight();
}
public void setViewHeight(int viewHeight) {
this.viewHeight = viewHeight;
}
}
测试代码:


1 import java.util.ArrayList;
2 import java.util.List;
3
4 import net.yunstudio.util.view.treeview.TreeItem;
5 import net.yunstudio.util.view.treeview.TreeView;
6 import android.os.Bundle;
7 import android.app.Activity;
8 import android.view.Menu;
9 import android.view.View;
10 import android.view.View.OnClickListener;
11 import android.widget.Button;
12
13 public class MainActivity extends Activity {
14
15 private TreeView treeView;
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.activity_main);
20 treeView=(TreeView) findViewById(R.id.treeview);
21 List<TreeItem> items=new ArrayList<TreeItem>();
22
23 initData(items);
24 treeView.initData(items, 0);
25 treeView.enabledAnim(500);
26
27 }
28 //初始化一些测试数据
29 public void initData(List<TreeItem> items) {
30 for (int i=0;i<10;i++) {
31 // TextView tv=new TextView(getActivity());
32 Button button=new Button(this);
33 button.setText("item"+i);
34 final TreeItem item=new TreeItem(button, null);
35
36 button.setOnClickListener(new OnClickListener() {
37 @Override
38 public void onClick(View v) {
39 treeView.bind(item);
40 }
41 });
42
43 items.add(item);
44
45 if(i%2==0){
46 Button bt1=new Button(this);
47 bt1.setText("item"+i);
48 bt1.setClickable(true);
49 final TreeItem item_1=new TreeItem(bt1, item);
50
51 bt1.setOnClickListener(new OnClickListener() {
52 @Override
53 public void onClick(View v) {
54
55 treeView.bind(item_1);
56 }
57 });
58 items.add(item_1);
59
60 if(i%4==0){
61 Button bt_2=new Button(this);
62 bt_2.setText("item"+i);
63 bt_2.setClickable(true);
64 final TreeItem item_2=new TreeItem( bt_2, item_1);
65
66 bt_2.setOnClickListener(new OnClickListener() {
67 @Override
68 public void onClick(View v) {
69 treeView.bind(item_2);
70 }
71 });
72 items.add(item_2);
73 }
74
75 }
76
77
78 }
79 }
80
81 @Override
82 public boolean onCreateOptionsMenu(Menu menu) {
83 // Inflate the menu; this adds items to the action bar if it is present.
84 getMenuInflater().inflate(R.menu.main, menu);
85 return true;
86 }
87
88 }
源码下载:https://github.com/yzhen334/android_treeview