2011年6月21日火曜日

Androidで ツリー表示可能なリストビュー

はじめまして。アクアキャスト島田です。
アンドロイド端末のアプリを作成している者です。

仕事でGUIを考えているとき、「ツリー表示ができるビューはないのか!?」と思い探してみました。
GoogleCodeで検索すると出てきたのですが、やはり自分で作ってしまおうと思い作ってみました。

まずはコアとなるアダプタクラスです。(ビュークラスはListViewを使います)

import java.util.ArrayList;
import java.util.List;
import android.widget.BaseAdapter;

public abstract class BaseTreeAdapter extends BaseAdapter {
    TreeEntry rootEntry = null; 

    public TreeEntry add(Object entry) {
        if(rootEntry == null) rootEntry = new TreeEntry();
        return rootEntry.add(entry);
    }

    @Override
    public int getCount() {
      return rootEntry == null ? 0 : rootEntry.getCount();
    } 

 @Override
 public Object getItem(int position) {
  // position=0はrootEntryとなってしまうため  
  // インクリメントする  
  return rootEntry == null ? null : rootEntry.getItem(position + 1) ; 
 } 

 @Override
 public long getItemId(int position) {
  //TreeEntry treeEntry = (TreeEntry)getItem(position);
  //return treeEntry.getId();
  return 0;
 } 

 public class TreeEntry {
  private List treeEntries = null;  
  private int depth = -1; // マイナスはルートオブジェクトのみ  

  private boolean isExpanded = false;
  private Object data = null;
  private TreeEntry() {   // ルートオブジェクト専用   
   isExpanded = true;
  }  

  private TreeEntry(TreeEntry parentEntry, Object data) {
   if(parentEntry != null) depth = parentEntry.depth + 1;
   this.data = data;
  }  

  public TreeEntry add(Object entry) {   
   if(treeEntries == null) treeEntries = new ArrayList();
   TreeEntry treeEntry = new TreeEntry(this, entry);
   treeEntries.add(treeEntry);
   return treeEntry;
  }  

  private int getCount() {
   if(treeEntries == null || !isExpanded) return 0;
   // 自分の持っているエントリの内、   
   // 開いている数を返す   
   int count = treeEntries.size();
   for(TreeEntry entry : treeEntries) count += entry.getCount();
   return count;  
  }  

  private TreeEntry getItem(int position) {
   if(position == 0) return this; // position=0は自分を返す
   if(treeEntries == null || !isExpanded) return null;
   for(int i = 0; position >= 0 && i < treeEntries.size(); ++ i) {
    TreeEntry entry = treeEntries.get(i);
    -- position;    // 子について、
    // エントリが取得できない場合、
    // 孫をpositionから引く  
    int count = entry.getCount();
    if(count >= position) {
     // 子以降で必ずアイテムが見つかる
     return entry.getItem(position);
    }
    position -= count;
   }
   // ここは、アイテム数より大きなpositionを
   // 指定された場合のみ
   return null;
  }  

  public void expand() {
   if(!hasChild()) return; // 子を持っていない場合、開けない
   if(isExpanded) return;
   isExpanded = true;
   notifyDataSetChanged();
  }

  public void collapse() {
   if(!isExpanded) return;
   isExpanded = false;
   notifyDataSetChanged();
  }

  public boolean isExpanded() {
   return isExpanded;
  }

  public int getDepth() {
   return depth;
  }
  
  public Object getData() {
   return data;
  }

  public boolean hasChild() {
   return treeEntries != null && !treeEntries.isEmpty();
  } 
 }

}

次に上記アダプタクラスから派生したクラスです。
public class MyTreeAdapter extends BaseTreeAdapter { 
 private LayoutInflater inflater = null;

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
   if(inflater == null) {
    inflater = (LayoutInflater)parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   }
   convertView = inflater.inflate(R.layout.entry, null);
  }

  TreeEntry treeEntry = (TreeEntry)getItem(position);
  TextView text = (TextView)convertView.findViewById(R.id.text);
  Object data = treeEntry.getData();
  String str = (String)data;
  text.setText(str);
  text.setPadding(treeEntry.getDepth() * 20, text.getPaddingTop(), text.getPaddingRight(), text.getPaddingBottom());

  return convertView;
 }
}

最後にアクティビティクラスです。

public class Main extends Activity { 
 private Map expandMap = new HashMap();

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  final ListView listView = (ListView)findViewById(R.id.list);

  listView.setOnItemClickListener(new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
     TreeEntry treeEntry = (TreeEntry)parent.getItemAtPosition(position);
     int depth = treeEntry.getDepth();
     if(treeEntry.isExpanded()) {
      expandMap.remove(depth);
      treeEntry.collapse(); 
     } else {
      if(treeEntry.hasChild()) {
       if(expandMap.containsKey(depth)) {
        expandMap.get(depth).collapse();
       }
       treeEntry.expand();
       expandMap.put(depth, treeEntry);
      }
     }
    }
   } );

  listView.setAdapter(new MyTreeAdapter());
  MyTreeAdapter adapter = (MyTreeAdapter)listView.getAdapter();

  TreeEntry root = adapter.add("1階層目-1");
  TreeEntry days = root.add("2階層目-1");
  days = root.add("2階層目-2");
  days.add("3階層目-1");
  days.add("3階層目-2");
  root = adapter.add("1階層目-2");
  days = root.add("2階層目-3");
  days.add("3階層目-3");
  days.add("3階層目-4");
  days.add("3階層目-5");
  days = root.add("2階層目-4");
  days.add("3階層目-6");
  days.add("3階層目-7");
  days.add("3階層目-8");
  days.add("3階層目-9");
 }
}

実行した画面をキャプチャーしました。

アダプタクラスだけで、できてしまいました。




投稿者:島田

1 件のコメント: