SoFunction
Updated on 2025-04-10

The treeview of the Android tree control that I implemented by myself

1. Development reasons
Often in projects, one requires a tree-like frame, which is a very common control. However, it may be that Google only provides an ExpandableListView with only a secondary level considering that Android is a mobile phone system and has limited interface width. Although this control can meet many needs, countless levels of trees are still needed in some cases, so I spent a day (most of the time I spent debugging animations, but the animation still has some problems now, and the specific reason is unknown. Thank you very much if a great master can find the reason).

2. Principle

Many online implementations are implemented by extending listviews, but listview does not seem to support events with complex controls? Moreover, it is not convenient to do animations, so I decided to extend linearlayout and add child nodes to implement it.

3. Code

:

Copy the codeThe code is as follows:

 package ;

 import ;
 import ;
 import ;
 import ;

 import ;
 import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

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();
    }
    /**
     * 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(List<TreeItem> items,int index){

        if(items==null||()==0){
            return ;
        }

        sortItemList(items);

        int size=();

        initAddIndex=index<0?-Integer.MAX_VALUE:index;

        for (int i=0;i<size;i++) {
            TreeItem item=(i);
            recuseShow(item);
        }

    }

    private boolean isAnim=false;
    /**
* This method has a very serious bug and cannot be used. .
* Set to 0 to turn off the animation
     * @param animTime
     */
    public void enabledAnim(int animTime) {
        if(animTime<0){
            isAnim=false;
            return ;
        }
        =animTime;
        isAnim=true;
    }

    private int initAddIndex;

    private void recuseShow(TreeItem item){
        View view=();
        addView(view,initAddIndex);
        if(()!=null){
            ();
            =false;
        }else {
            ();
            =true;
        }
        initAddIndex++;
        List<TreeItem> childrens=();
        if(()>0){
            for (TreeItem it : childrens) {
                recuseShow(it);
            }
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        (hasWindowFocus);
        if(hasWindowFocus){

        }
    }

   private void sortItemList(List<TreeItem> items) {
//Stored items according to hierarchical relationships, sortedItems only store the top-level items
       sortedItems=new ArrayList<TreeItem>(5);
       for (TreeItem item : items) {
           if(()==null){
               (item);
           }else {
               ().getChildrens().add(item);
           }
       }

   }

  
   private int viewIndex=0;
   private int animHeight=0;
   private void addChild(TreeItem item,boolean isRecurse){
       if(().size()>0){
           List<TreeItem> list=();
           for (TreeItem it :    list) {
               View view=();
               if(){
                   continue;
               }
               viewIndex++;
               ();
               =true;
               if(isAnim){
                   animHeight-=();
               }
               =true;
               if(isRecurse){
                   addChild(it,true);
               }
           }
       }
   }
   private int removeCount=0;
   private synchronized void removeChild(TreeItem item,boolean isRecurse){
       if(().size()>0){
           List<TreeItem> list=();
           for (TreeItem it :    list) {
               View view=();
               if(!){
                   continue;
               }
//                removeViewAt(viewIndex);

               TranslateAnimation ta=new TranslateAnimation(0, 0, 0, 0);
               (true);
               (1000);
               (ta);
//                viewIndex++;
               removeCount++;
               =false;
               ();
               if(isAnim){
                   animHeight+=();
               }
               if(isRecurse){
                   removeChild(it,true);
               }
           }
       }
   }

   private void animAdd(){
       TranslateAnimation ta=new TranslateAnimation(
               , 0,
               , 0,
               , animHeight,
               , 0);
       (true);
//        (false);
       (animTime);

       for (int i = viewIndex+1; i < getChildCount(); i++) {
           View view=getChildAt(i);
           (ta);
       }
       animHeight=0;
   }
   private void animRemove(){
       TranslateAnimation ta=new TranslateAnimation(
               , 0,
               , 0,
               , animHeight,
               , 0);
       (false);
       (true);
       (animTime);

       int startAnimIndex;
       startAnimIndex=viewIndex+1;
       for (int i = startAnimIndex; i < getChildCount(); i++) {
           View view=getChildAt(i);

           (ta);
       }
       animHeight=0;
   }
   public void expand(TreeItem item){
       viewIndex=indexOfChild(());
       addChild(item,false);
       if(isAnim){
           animAdd();
       }
   }
  

   public void expandAllChildren(TreeItem item) {
       viewIndex=indexOfChild(());
       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(())+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(){
           expand(item);
       }else {
           contractAllChildren(item);
       }
       =!;
   } 
}


Copy the codeThe code is as follows:

package ;

import ;
import ;

import ;

public class TreeItem {
    private View view;
   private TreeItem parent;
   private List<TreeItem> childrens=new ArrayList<TreeItem>(0);
   public boolean nextIsExpand=true;
   public boolean isShow=false;
   private int viewHeight; 

  
   public TreeItem(){}
   public TreeItem(int id,View view, TreeItem parent) {
       super();
       = view;
       = parent;
   }

   public View getView() {
       if(view!=null){
           (getLevel()*20,0,0,0);
       }
       return view;
   }

   public void setView(View view) {
       = view;
   }

   public TreeItem getParent() {
       return parent;
   }

   public void setParent(TreeItem parent) {
       = parent;
   }

   /**
* Dynamically obtain the series of this node
    * @return
    */
   public int getLevel() {
       int level=0;
       TreeItem localParent=parent;

       while (localParent!=null) {
           level++;
           localParent=();
       }

       return level;
   }
   public List<TreeItem> getChildrens() {
       return childrens;
   }
   public int getViewHeight() {
       if(view==null||()==0){
           return viewHeight;
       }
       return ();
   }
   public void setViewHeight(int viewHeight) {
       = viewHeight;
   }
}

Test code:

Copy the codeThe code is as follows:

package ;

import ;
import ;
import ;

import ;
import ;
import ;

import ;
import ;

public class MainActivity extends SlidingFragmentActivity {
   @Override
   public void onCreate(Bundle savedInstanceState) {
       (savedInstanceState);

       setContentView(.slid_content_frame);

               tv_notebook=(TreeView)   findViewById(.tv_notebook);

       List<TreeItem> items=new ArrayList<TreeItem>();

       initData(items);
       tv_notebook.initData(items, 0);
       tv_notebook.enabledAnim(500);

   }
//Initialize some test data
   public void initData(List<TreeItem> items) {
       for (int i=0;i<10;i++) {
//            TextView tv=new TextView(getActivity());
           Button button=new Button(getActivity());
           ("item"+i);
           final TreeItem item=new TreeItem(0, button, null);

           (new OnClickListener() {
               @Override
               public void onClick(View v) {
                   tv_notebook.bind(item);
               }
           });

           (item);

           if(i%2==0){
               Button bt1=new Button(getActivity());
               ("item"+i);
               (true);
               final TreeItem item_1=new TreeItem(0, bt1, null);

               (new OnClickListener() {
                   @Override
                   public void onClick(View v) {

                       tv_notebook.bind(item_1);
                   }
               });
               item_1.setParent(item);
               (item_1);

               if(i%4==0){
                   Button bt_2=new Button(getActivity());
                   bt_2.setText("item"+i);
                   bt_2.setClickable(true);
                   final TreeItem item_2=new TreeItem(0, bt_2, null);

                   bt_2.setOnClickListener(new OnClickListener() {
                       @Override
                       public void onClick(View v) {
                           tv_notebook.bind(item_2);
                       }
                   });
                   item_2.setParent(item_1);
                   (item_2);
               }

           }

          
       }
   }

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
       return true;
   }

}