階層構造をリストフラグメントで作ってみた

アプリで階層構造のリストを作成する機会があって、せっかくなのでサンプルとして残しておこうと思う。おそらく今後使用することはないかもだけど。やりたいこととしては、階層をDBとリストフラグメントを使って表示したい。作成したサンプルアプリは単純にタイトルをリストで表示するだけのもの。作成したものは以下の通り

何がやりたいかというと、下の図みたいなのをリストで表示したい。
hierarchy

DB

DBは「HierarchyList.db」というDBをあらかじめ作成しておき、「assets」フォルダにおいて、そこからコピーして使用する。テーブルレイアウトは以下のレイアウト。「Id」はユニークないID、親の階層を指定するための「ParentId」、一番上の階層の場合は「0」を指定。

CREATE TABLE HierarchyList (
	Id 		INTEGER PRIMARY KEY AUTOINCREMENT,
	ParentId	INTEGER,
	Title		TEXT
);

テーブルのデータは以下。取りあえず3階層まで作ってみた。

Id ParentId Title
1 0 1
2 0 2
3 0 3
4 0 4
5 0 5
6 1 1-1
7 1 1-2
8 1 1-3
9 1 1-4
10 1 1-5
11 2 2-1
12 2 2-2
13 2 2-3
14 2 2-4
15 2 2-5
16 3 3-1
17 3 3-3
18 3 3-3
19 3 3-4
20 3 3-5
21 4 4-1
22 4 4-4
23 4 4-4
24 4 4-4
25 4 4-5
26 5 5-1
27 5 5-5
28 5 5-5
29 5 5-5
30 5 5-5
31 6 1-1-1
32 6 1-1-2
33 6 1-1-3
34 6 1-1-4
35 6 1-1-5
36 7 1-2-1
37 7 1-2-2
38 7 1-2-3
39 7 1-2-4
40 7 1-2-5
41 8 1-3-1
42 8 1-3-2
43 8 1-3-3
44 8 1-3-4
45 8 1-3-5
46 9 1-4-1
47 9 1-4-2
48 9 1-4-3
49 9 1-4-4
50 9 1-4-5
51 10 1-5-1
52 10 1-5-2
53 10 1-5-3
54 10 1-5-4
55 10 1-5-5

プログラム

  • HierarchyListFragment.java
  • ListItem.java
  • DatabaseHelper.java
  • MainActivity.java

MainActivity.java

import android.app.Activity;
import android.app.FragmentTransaction;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// リストフラグメントへのパラメータ(ルート)
		Bundle bundle = new Bundle();
		bundle.putInt("parent_id", 0);
		
		// リストフラグメントのセット
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        HierarchyListFragment listFragment = new HierarchyListFragment();
		listFragment.setArguments(bundle);
        ft.replace(R.id.fragment_container, listFragment);
        ft.commit();
	}
	
	/**
     * リストフラグメントアイテムクリックイベント
     */	
	public void onListItemClick(int itemId) {
		// 下階層のチェック
		DatabaseHelper dbHelper = new DatabaseHelper(this);
		SQLiteDatabase db = dbHelper.getReadableDatabase();
	    String query = "SELECT * FROM HierarchyList WHERE ParentId = " + itemId + ";";
	    Cursor c = db.rawQuery(query, null);            
	    if (! c.moveToFirst()) {
	    	Toast.makeText(this, "これより下の階層はありません", Toast.LENGTH_SHORT).show();
	    	c.close();
	    	return;
	    }
	    c.close();
		
		// リストフラグメントへのパラメータ
		Bundle bundle = new Bundle();
		bundle.putInt("parent_id", itemId);
					
		// 次の階層のリストフラグメントの生成
		FragmentTransaction ft = getFragmentManager().beginTransaction();
		HierarchyListFragment listFragment = new HierarchyListFragment();
		listFragment.setArguments(bundle);
        ft.replace(R.id.fragment_container, listFragment);
        ft.addToBackStack(null);
        ft.commit();
	}
}

HierarchyListFragment.java

import java.util.ArrayList;
import java.util.List;
import android.app.ListFragment;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class HierarchyListFragment extends ListFragment {
	private ArrayAdapter<ListItem> adapter;
	private int parentId = 0;
	
	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		
		// パラメータのセット
		parentId = getArguments().getInt("parent_id");
		
		// リストのセット
		setListView();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		return inflater.inflate(R.layout.list_fragment, container, false);
	}
	
	@Override
	public void onListItemClick(ListView listView, View v, int position, long id) {
    	super.onListItemClick(listView, v, position, id);
    	
    	// Activityに処理を渡す
    	MainActivity activity = (MainActivity)getActivity();
    	activity.onListItemClick(adapter.getItem(position).getId());
	}
	
	/**
	 * リストのセット
	 */
	public void setListView() {
		// リストのセット
		List<ListItem> list = new ArrayList<ListItem>();
		DatabaseHelper dbHelper = new DatabaseHelper(getActivity());
		SQLiteDatabase db = dbHelper.getReadableDatabase();
	    String query = "SELECT * FROM HierarchyList"
	    					+ " WHERE ParentId = " + parentId + " ORDER BY Id;";
	    Cursor c = db.rawQuery(query, null);            
	    if (c.moveToFirst()) {
	    	do {
	    		ListItem listItem = new ListItem();
	    		listItem.setId(c.getInt(c.getColumnIndex("Id")));
	    		listItem.setTitle(c.getString(c.getColumnIndex("Title")));
	    		listItem.setParentId(c.getInt(c.getColumnIndex("ParentId")));
	    		list.add(listItem);
	    	} while (c.moveToNext());
		}
	    c.close();
		
	    // アダプターのセット
	    adapter = new ListAdapter(getActivity(), list);
	    setListAdapter(adapter);
	}
	
	/**
	 * ListAdapter Class
	 */
	private class ListAdapter extends ArrayAdapter<ListItem> {
		private LayoutInflater mInflater;
		
		public ListAdapter(Context context, List<ListItem> objects) {
			super(context, 0, objects);
			mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		}
	
		public View getView(int position, View convertView, ViewGroup parent) {
			final ViewHolder holder;
			
			if (convertView == null) {
				convertView = mInflater.inflate(R.layout.listfragment_item, parent, false);
				holder = new ViewHolder();
				holder.tvTittle = (TextView)convertView.findViewById(R.id.title);
				convertView.setTag(holder);
			} else {			
				holder = (ViewHolder)convertView.getTag();
			}
			final ListItem item = this.getItem(position);
			
			holder.tvTittle.setText(item.getTitle());
			
			return convertView;
		}
	} 
	
	/**
	 * ViewHolder for ListView
	 */	
	private class ViewHolder {
		TextView tvTittle;
	}
}

後は省略

レイアウトファイル

作成したレイアウトファイルは以下の通り

  • activity_main.xml
  • fragment_main.xml
  • list_fragment.xml
  • listfragment_item.xml

プログラムの実行

以下のようになる。リストをクリックすると下の階層に行く。
device-2014-06-21-230946device-2014-06-21-230958device-2014-06-21-231007

作成したサンプルプログラム
HierarchyListFragmentSample

sqliteでファイルの違うDBで結合をしたい

androidのSQLiteでDBファイルが違うファイルを結合したい。例えば郵便番号とか都道府県データの静的なDBと動的なユーザー用のDBがあって、静的なDBはassetsからコピー、動的なユーザー用DBは普通に追加訂正削除していく。で、これらを結合したい。調べたら「ATTACH DATABASE」というのを使えばできるみたい。

「DatabaseHelper1」は動的なDB、「DatabaseHelper2」は静的なDBとする。
「DatabaseHelper1」のテーブル「Table1」の列「Id」で「DatabaseHelper2」のテーブル「Table2」で列「Title」を検索すると、以下になる。

DatabaseHelper1 dbHelper = new DatabaseHelper1(this);
SQLiteDatabase db = dbHelper.getReadableDatabase();
db.execSQL("ATTACH DATABASE '" + this.getDatabasePath(DatabaseHelper2.DATABASE_NAME) + "' AS DB2");
String query = "SELECT A.Id, B.Title FROM Table1 A INNER JOIN DB2.Table2 B WHERE A.Id = B.Id;";
Cursor c = db.rawQuery(query, null);

最初にこれをやろうとしたところエラーが出た。内容はText Encordingを合わせる必要があると、エンコーディングは以下のコマンドでわかる。

pragma encoding;

で、sqlite3用ツールで作成した静的DBは「UTF-16le」で、android内で作成した動的DBは「UTF-8」だった。既存の「UTF-16le」のDBを「UTF-8」に変換する方法はわからなかったので(たぶんできない?)、「UTF-8」でDBを作成し直して、データをインポートしたら動いた。

DeployGateを使ってみた。

複数人で開発を行うツール「DeployGate」に登録して使用してみた。
今まではapkファイルをメールで送ってってやってたけど、このツールを使用すれば通知が行って最新のをインストールできるので、わざわざメールで送る必要はない。あとはメッセージのやりとりや、ログも取れるみたい。便利なので使用を続けることにしたんだけど、月500円の有料プランの「ライト」が売り切れになってる。次に安いのは月3,500円。とりあえず無料版を使い続けることにする。