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

图片是gif,真实效果要更加流畅一些,但是bug也还在的,就是开始隐藏的节点无法获取高度,可以通过自己事先设置解决。
3.代码
TreeView.java:
package net.memornote.android.ui.view; import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask; import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout; public class TreeView extends LinearLayout{ // private List<TreeItem> items;
private List<TreeItem> sortedItems;
private int animTime; public TreeView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
}
/**
* initialize data,you must make sure that each item has parent except the top ones.
* @param items the data to show
* @param index the index of the tree to insert to
* @param viewHeight each view's height
* @param animTime if you want expand animation,
* you can set the time(ms) of animation,otherwise you can set 0.
*
*/
public void initData(Collection<TreeItem> items,int index){ if(items==null||items.size()==0){
return ;
} sortItemList(items); int size=sortedItems.size(); initAddIndex=index<0?-Integer.MAX_VALUE:index; for (int i=0;i<size;i++) {
TreeItem item=sortedItems.get(i);
recuseShow(item);
} } private boolean isAnim=false;
/**
* 这个方法还有很 严重的bug,无法使用。。
* 设置为0则关闭动画
* @param animTime
*/
public void enabledAnim(int animTime) {
if(animTime<0){
isAnim=false;
return ;
}
this.animTime=animTime;
isAnim=true;
} private int initAddIndex; private void recuseShow(TreeItem item){
View view=item.getView();
addView(view,initAddIndex);
if(item.getParent()!=null){
view.setVisibility(View.GONE);
}else {
view.setVisibility(View.VISIBLE);
}
initAddIndex++;
List<TreeItem> childrens=item.getChildrens();
if(childrens.size()>0){
for (TreeItem it : childrens) {
recuseShow(it);
}
}
} private void sortItemList(Collection<TreeItem> items) {
//把items按照层级关系存放,sortedItems只存放顶层的item
sortedItems=new ArrayList<TreeItem>(5);
for (TreeItem item : items) {
if(item.getParent()==null){
sortedItems.add(item);
}else {
item.getParent().getChildrens().add(item);
}
} } private int viewIndex=0;
private int animHeight=0;
private void addChild(TreeItem item,boolean isRecurse){
if(item.getChildrens().size()>0){
List<TreeItem> list=item.getChildrens();
for (TreeItem it : list) {
View view=it.getView();
if(view.isShown()){
continue;
}
viewIndex++;
view.setVisibility(View.VISIBLE);
if(isAnim){
animHeight-=it.getViewHeight();
}
it.nextIsExpand=true;
if(isRecurse){
addChild(it,true);
}
}
}
}
private int removeCount=0;
private synchronized void removeChild(TreeItem item,boolean isRecurse){
if(item.getChildrens().size()>0){
List<TreeItem> list=item.getChildrens();
for (TreeItem it : list) {
View view=it.getView();
if(!view.isShown()){
continue;
} TranslateAnimation ta=new TranslateAnimation(0, 0, 0, 0);
ta.setFillBefore(true);
ta.setDuration(1000);
view.startAnimation(ta);
removeCount++;
view.setVisibility(View.GONE);
if(isAnim){
animHeight+=it.getViewHeight();
}
if(isRecurse){
removeChild(it,true);
}
}
}
} private void animAdd(){
TranslateAnimation ta=new TranslateAnimation(
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, animHeight,
Animation.ABSOLUTE, 0);
ta.setFillBefore(true);
ta.setFillAfter(false);
ta.setDuration(animTime); for (int i = viewIndex+1; i < getChildCount(); i++) {
View view=getChildAt(i);
if(view.isShown()){
view.startAnimation(ta);
}
}
animHeight=0;
}
private void animRemove(){
TranslateAnimation ta=new TranslateAnimation(
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, animHeight,
Animation.ABSOLUTE, 0);
ta.setFillAfter(false);
ta.setFillBefore(true);
ta.setDuration(animTime); int startAnimIndex;
startAnimIndex=viewIndex+1;
for (int i = startAnimIndex; i < getChildCount(); i++) {
View view=getChildAt(i);
if(view.isShown()){
view.startAnimation(ta);
}
}
animHeight=0;
}
public void expand(TreeItem item){
viewIndex=indexOfChild(item.getView());
addChild(item,false);
if(isAnim){
animAdd();
}
} public void expandAllChildren(TreeItem item) {
viewIndex=indexOfChild(item.getView());
addChild(item,true);
if(isAnim){
animAdd();
}
} public void expandAll(){
if(sortedItems==null){
return ;
}
for (TreeItem item : sortedItems) {
expandAllChildren(item);
}
} public void contractAllChildren(TreeItem item) {
viewIndex=indexOfChild(item.getView())+1;
removeChild(item,true);
if(isAnim){
animRemove();
}
} public void contractAll(){
if(sortedItems==null){
return ;
}
for (TreeItem item : sortedItems) {
contractAllChildren(item);
}
} public void bind(TreeItem item) {
if(item.nextIsExpand){
expand(item);
}else {
contractAllChildren(item);
}
item.nextIsExpand=!item.nextIsExpand;
} }
TreeView
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;
} }
TreeItem
测试代码:
import java.util.ArrayList;
import java.util.List; import net.yunstudio.util.view.treeview.TreeItem;
import net.yunstudio.util.view.treeview.TreeView;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button; public class MainActivity extends Activity { private TreeView treeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
treeView=(TreeView) findViewById(R.id.treeview);
List<TreeItem> items=new ArrayList<TreeItem>(); initData(items);
treeView.initData(items, 0);
treeView.enabledAnim(500); }
//初始化一些测试数据
public void initData(List<TreeItem> items) {
for (int i=0;i<10;i++) {
// TextView tv=new TextView(getActivity());
Button button=new Button(this);
button.setText("item"+i);
final TreeItem item=new TreeItem(button, null); button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
treeView.bind(item);
}
}); items.add(item); if(i%2==0){
Button bt1=new Button(this);
bt1.setText("item"+i);
bt1.setClickable(true);
final TreeItem item_1=new TreeItem(bt1, item); bt1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) { treeView.bind(item_1);
}
});
items.add(item_1); if(i%4==0){
Button bt_2=new Button(this);
bt_2.setText("item"+i);
bt_2.setClickable(true);
final TreeItem item_2=new TreeItem( bt_2, item_1); bt_2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
treeView.bind(item_2);
}
});
items.add(item_2);
} } }
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }
源码下载:https://github.com/yzhen334/android_treeview
自己实现的android树控件,android TreeView的更多相关文章
- Android 实现自己定义多级树控件和全选与反选的效果
博文開始之前,首先要感谢大牛:(lmj623565791),本博文是在其博文http://blog.csdn.net/lmj623565791/article/details/40212367基础上进 ...
- Android 基本控件相关知识整理
Android应用开发的一项重要内容就是界面开发.对于用户来说,不管APP包含的逻辑多么复杂,功能多么强大,如果没有提供友好的图形交互界面,将很难吸引最终用户.作为一个程序员如何才能开发出友好的图形界 ...
- 【Android开发日记】之入门篇(十三)——Android的控件解析
Android的控件都派生自android.view.View类,在android.widget包中定义了大量的系统控件供开发者使用,开发者也可以从View类及其子类中,派生出自定义的控件. 一.An ...
- Android - 控件android:ems属性
Android - 控件android:ems属性http://blog.csdn.net/caroline_wendy/article/details/41684255?utm_source=tui ...
- android 基础控件(EditView、SeekBar等)的属性及使用方法
android提供了大量的UI控件,本文将介绍TextView.ImageView.Button.EditView.ProgressBar.SeekBar.ScrollView.WebView ...
- Android基本控件之Menus
在我们的手机中有很多样式的菜单,比如:我们的短信界面,每条短信,我们长按都会出现一个菜单,还有很多的种类.那么现在,我们就来详细的讨论一下安卓中的菜单 Android的控件中就有这么一个,叫做Menu ...
- Android:控件布局(相对布局)RelativeLayout
RelativeLayout是相对布局控件:以控件之间相对位置或相对父容器位置进行排列. 相对布局常用属性: 子类控件相对子类控件:值是另外一个控件的id android:layout_above-- ...
- Android:控件布局(线性布局)LinearLayout
LinearLayout是线性布局控件:要么横向排布,要么竖向排布 决定性属性:必须有的! android:orientation:vertical (垂直方向) .horizontal(水平方向) ...
- 矩阵, 矩阵 , Android基础控件之ImageView
天下文章大家抄,以下所有内容,有来自copy,有来自查询,亦有自己的总结(目的是总结出自己的东西),所以说原创,不合适,说是转载也不恰当,所以我称之为笔记,可惜没有此分类选项,姑且不要脸一点,选择为原 ...
随机推荐
- Linux 命令 - chown: 更改文件的所有者和所属群组
chown 命令用来更改文件或者目录的所有者和所属群组.使用这个命令需要超级用户的权限. 命令格式 chown [OPTION]... [OWNER][:[GROUP]] FILE... chown ...
- Linux 命令 - mv: 移动或重命名文件
命令格式 cp [OPTION]... [-T] SOURCE DEST cp [OPTION]... SOURCE... DIRECTORY cp [OPTION]... -t DIRECTORY ...
- 【Knockout】五、创建自定义绑定
概述 除了上一篇列出的KO内置的绑定类型(如value.text等),你也可以创建自定义绑定. 注册你的binding handler ko.bindingHandlers.yourBindingNa ...
- JAVA语法之小结
对于JAVA的语法,我做了个小节: 类名:所有类名称首字母大写,如果由几个单词组成,那么组合内的第一个单词首字母应当大写,可以包括数字但是不能以数字开头. 方法名:方法没应当小写,如果由几个单词组成, ...
- Cocos2d-x实例:设置背景音乐与音效- AppDelegate实现
为了进一步了解背景音乐和音效播放的,我们通过一个实例给大家介绍一下.如下图所示有两个场景:HelloWorld和Setting.在HelloWorld场景点击“游戏设置”菜单可以切换到Setting场 ...
- C# 微信扫码支付 回调页面
.NET版 微信扫码支付,官方推荐使用[模式二] 一.微信扫码支付模式一: 1.回调页面:官方demo中example文件下的NativeNotifyPage.aspx 2.微信回调地址:http:/ ...
- (转)Redis 集群方案
根据一些测试整理出来的一份方案: 1. Redis 性能 对于redis 的一些简单测试,仅供参考: 测试环境:Redhat6.2 , Xeon E5520(4核)*2/8G,1000M网卡 Redi ...
- 暑假集训(5)第一弹——— Super Jumping! Jumping! Jumping!(hdu1087)
题意概括:在上次与娑殚的三次博弈中,你们都取得了胜利.便向娑殚提出要求,借助他的力量,传送到一个安全的地方. 你们的愿望达成了,不过,你和小A似乎失散了. 街上人来人往的特别热闹,每一个人的脸上都洋溢 ...
- CSMA-CA介绍
本文主要介绍通讯领域中CSMA相关机制,本文全部资料来自于网络. 网络通讯,必须依靠介质来传递数据,将数据调制到模拟信号上,再把此信号通过介质传递到远方.根据介质的不同,分为有线网络和无线网络.为 ...
- hadoop下跑mapreduce程序报错
mapreduce真的是门学问,遇到的问题逼着我把它从MRv1摸索到MRv2,从年前就牵挂在心里,连过年回家的旅途上都是心情凝重,今天终于在eclipse控制台看到了job completed suc ...