今回のポイント
・ディレクトリ選択モードとの使い分けができる
(モードは途中で切り替えできません)
・ファイルの一覧表示部分をカスタムビューとして作った
・戻るキーでディレクトリをさかのぼれる
(設定でOn/Offできる)
です。
スクリーンショットはこんな感じ
発展目標として
・表示順のソート
・表示するファイルの種類をフィルタ
・アイコンを付ける
でしょうか。
いつもどおりGoogleCodeにクラスがあります。
「FileListDialog」
「FileListView」
ダイアログの使い方
FileListDialog dialog = new FileListDialog(this); dialog.setDirectorySelect(false); dialog.setOnFileListDialogListener(new onFileListDialogListener() { @Override public void onClickFileList(File file) { if(file == null){ //not select }else{ //select file or directory } } }); dialog.show("/", "select");
カスタムビューの使い方
<jp.xii.relog.customlibrary.widget.FileListView android:layout_width="fill_parent" android:layout_height="wrap_content" select_dir="true" default_dir="/mnt" dispatch_back="false" />
FileListDialogクラス
package jp.xii.relog.customlibrary.app; import java.io.File; import jp.xii.relog.customlibrary.Utility; import jp.xii.relog.customlibrary.widget.FileListView; import jp.xii.relog.customlibrary.widget.FileListView.onFileListListener; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; /** * ファイルリストダイアログクラス * @author Iori * */ public class FileListDialog implements onFileListListener { private Context _parent = null; //親 private File _currentFile = null; //現在の選択 private onFileListDialogListener _listener = null; //リスナー private boolean _isDirectorySelect = false; //ディレクトリ選択をするか? private CustomAlertDialog _dialog = null; /** * ディレクトリ選択をするか? * @param is */ public void setDirectorySelect(boolean is){ _isDirectorySelect = is; } public boolean isDirectorySelect(){ return _isDirectorySelect; } /** * 選択されたファイル名取得 * @return */ public String getSelectedFileName(){ String ret = ""; if(_currentFile != null){ ret = _currentFile.getAbsolutePath(); } return ret; } /** * ファイル選択ダイアログ * @param context 親 */ public FileListDialog(Context context){ _parent = context; } /** * ダイアログ表示 * @param context 親 * @param path 表示したいディレクトリ * @param title ダイアログのタイトル */ public void show(String path, String title){ if(path == null){ path = Utility.getSdcardPath(); }else if(path.length() == 0){ path = Utility.getSdcardPath(); } FileListView list = new FileListView(_parent, new File(path), isDirectorySelect()); list.setOnFileListListener(this); _dialog = new CustomAlertDialog(_parent); _dialog.setTitle(title); _dialog.setView(list); _dialog.setButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //nop } }); _dialog.show(); } /** * リスナーのセット * @param listener */ public void setOnFileListDialogListener(onFileListDialogListener listener){ _listener = listener; } /** * ダイアログでファイルが選択された */ @Override public void onSelectFile(File file) { _currentFile = file; if(_dialog != null){ _dialog.dismiss(); _dialog = null; } if(_listener != null){ _listener.onClickFileList(file); } } /** * ダイアログでディレクトリが選択された */ @Override public void onSelectDirectory(File file) { _currentFile = file; if(_dialog != null){ _dialog.dismiss(); _dialog = null; } if(_listener != null){ _listener.onClickFileList(file); } } /** * ディレクトリが変更された */ @Override public void onChangeDirectory(File file) { //nop } /** * クリックイベントのインターフェースクラス * @author Iori * */ public interface onFileListDialogListener{ public void onClickFileList(File file); } /** * カスタムダイアログ * @author Iori * */ public class CustomAlertDialog extends AlertDialog { protected CustomAlertDialog(Context context) { super(context); } protected CustomAlertDialog(Context context, int theme) { super(context, theme); } protected CustomAlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { super(context, cancelable, cancelListener); } } }
FileListViewクラス
package jp.xii.relog.customlibrary.widget; import java.io.File; import java.util.ArrayList; import java.util.Stack; import android.content.Context; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import jp.xii.relog.customlibrary.R; import jp.xii.relog.customlibrary.Utility; public class FileListView extends ViewGroup implements AdapterView.OnItemClickListener , View.OnClickListener{ //属性名 public final static String STR_ATTR_SELECT_DIR = "select_dir"; //ディレクトリ選択 public final static String STR_ATTR_DEFAULT_DIR = "default_dir"; //初期ディレクトリ public final static String STR_ATTR_DISPATCH_BACK_KEY = "dispatch_back"; //戻るキーを受けるか private View _mainView = null; private boolean _isSelectDir = false; //ディレクトリ選択 private File _currentDir = null; //カレントディレクトリ private ArrayList<File> _currentDirFileList = null; //現在のディレクトリのファイルの一覧 private Stack<File> _historyFileList = null; //たどったパスのスタック private boolean _isDispatchBackKey = true; //戻るキーを受けるか private onFileListListener _listener = null; //リスナ /** * ディレクトリ選択 * @param _isSelectDir the _isSelectDir to set * 後から変更不可 */ private void setIsSelectDir(boolean _isSelectDir) { this._isSelectDir = _isSelectDir; } /** * ディレクトリ選択 * @return the _isSelectDir */ public boolean isSelectDir() { return _isSelectDir; } /** * カレントディレクトリ * @param _currentDir the _currentDir to set */ public void setCurrentDirectory(File _currentDir) { this._currentDir = _currentDir; } /** * カレントディレクトリ * @return the _currentDir */ public File getCurrentDirectory() { if(_currentDir == null){ _currentDir = new File(Utility.getSdcardPath()); } return _currentDir; } /** * 現在のディレクトリのファイルの一覧 * @return */ public ArrayList<File> getCurrentDirFileList() { if(_currentDirFileList == null){ _currentDirFileList = new ArrayList<File>(); } return _currentDirFileList; } /** * たどったパスのスタック * @return the _historyFileList */ public Stack<File> getHistoryFileList() { if(_historyFileList == null){ _historyFileList = new Stack<File>(); } return _historyFileList; } /** * 戻るキーを受けるか * @param _isDispatchBackKey the _isDispatchBackKey to set */ public void setIsDispatchBackKey(boolean _isDispatchBackKey) { this._isDispatchBackKey = _isDispatchBackKey; } /** * 戻るキーを受けるか * @return the _isDispatchBackKey */ public boolean isDispatchBackKey() { return _isDispatchBackKey; } /** * リスナ * @param _listener the _listener to set */ public void setOnFileListListener(onFileListListener _listener) { this._listener = _listener; } /** * リスナ * @return the _listener */ public onFileListListener getOnFileListListener() { return _listener; } /** * コンストラクタ * @param context * @param is_select_dir */ public FileListView(Context context, File default_dir, boolean is_select_dir){ super(context); setCurrentDirectory(default_dir); setIsSelectDir(is_select_dir); init(null); } /** * コンストラクタ * @param context * @param attrs */ public FileListView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } /** * 初期化 * @param attrs */ private void init(AttributeSet attrs){ String temp = null; if(attrs == null){ }else{ //ディレクトリ選択 setIsSelectDir(attrs.getAttributeBooleanValue(null, STR_ATTR_SELECT_DIR, false)); //初期ディレクトリ temp = attrs.getAttributeValue(null, STR_ATTR_DEFAULT_DIR); if(temp != null){ setCurrentDirectory(new File(temp)); }else{ //未指定の場合はmicroSD setCurrentDirectory(new File(Utility.getSdcardPath())); } //戻るキーを受けるか setIsDispatchBackKey(attrs.getAttributeBooleanValue(null, STR_ATTR_DISPATCH_BACK_KEY, true)); } //inflaterを使ってxmlのレイアウトをViewに反映する LayoutInflater inflater = (LayoutInflater)getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); int id = 0; if(isSelectDir()){ id = jp.xii.relog.customlibrary.R.layout.file_list_view_dir; }else{ id = jp.xii.relog.customlibrary.R.layout.file_list_view; } _mainView = inflater.inflate(id, null); //ディレクトリ選択イベント Button button = (Button)_mainView.findViewById(R.id.FileSelectListOK_Button); if(button != null){ button.setOnClickListener(this); } //リストビューのイベント ListView list = (ListView)_mainView.findViewById(R.id.FileList_ListView); if(list != null){ list.setOnItemClickListener(this); } //初期表示 viewFiles(getCurrentDirectory()); } /** * レイアウト確定 */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT , ViewGroup.LayoutParams.WRAP_CONTENT); addViewInLayout(_mainView, -1, lp); //子要素の描画範囲でレイアウトを作成する _mainView.layout(0, 0, _mainView.getMeasuredWidth(), _mainView.getMeasuredHeight()); } /** * サイズ計測 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //子要素に必要な大きさを計測させる _mainView.measure(widthMeasureSpec, heightMeasureSpec); int spec_width = MeasureSpec.getSize(widthMeasureSpec); int spec_height = _mainView.getMeasuredHeight(); //サイズ設定 setMeasuredDimension(spec_width, spec_height); } /** * キーイベント */ @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean ret = false; if(event.getKeyCode() == KeyEvent.KEYCODE_BACK){ if(!isDispatchBackKey()){ //キーは無視 }else if(event.getAction() == KeyEvent.ACTION_DOWN){ //戻るボタンが押された try{ File dir = getHistoryFileList().pop(); dir = getHistoryFileList().pop(); ret = viewFiles(dir); }catch(Exception e){ } } } if(ret){ return ret; }else{ return super.dispatchKeyEvent(event); } } /** * ファイル一覧を登録する * @param path */ private boolean viewFiles(File dir){ boolean ret = false; ListView list = (ListView)_mainView.findViewById(R.id.FileList_ListView); if(dir == null || list == null){ }else if(!dir.canRead()){ //読めない }else{ //アダプタ作成 ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext() , android.R.layout.simple_list_item_1); File[] file_lists = dir.listFiles(); //クリア getCurrentDirFileList().clear(); //現在のディレクトリ setCurrentDirectory(dir); viewCurrentDirectory(dir); if(file_lists == null){ }else{ //追加 for (File file : file_lists) { if(file.isDirectory()){ //ディレクトリの場合 adapter.add(file.getName() + "/"); getCurrentDirFileList().add(file); }else{ //通常のファイル if(isSelectDir()){ //ディレクトリ選択モードの時は何もしない }else{ adapter.add(file.getName()); getCurrentDirFileList().add(file); } } } } //空っぽ if(adapter.getCount() == 0){ adapter.add(getContext().getString(R.string.file_list_empty)); } //履歴に保存 getHistoryFileList().push(dir); //アダプタを設定 list.setAdapter(adapter); ret = true; } return ret; } /** * 現在のパスを表示する * @param dir */ private void viewCurrentDirectory(File dir){ TextView text = (TextView)_mainView.findViewById(R.id.FileListCurrentPath_TextView); if(dir == null || text == null){ }else{ text.setText(dir.getAbsolutePath()); } } /** * アイテムの選択 */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if(getCurrentDirFileList() == null){ }else if(getCurrentDirFileList().size() <= position){ }else{ File file = getCurrentDirFileList().get(position); if(file.isDirectory()){ //ディレクトリをたどる if(!viewFiles(file)){ file = null; //失敗はnullを通知する } if(getOnFileListListener() != null){ getOnFileListListener().onChangeDirectory(file); } }else{ //ファイルだったのでイベントで通知 if(getOnFileListListener() != null){ getOnFileListListener().onSelectFile(file); } } } } /** * ボタンが押された */ @Override public void onClick(View v) { switch(v.getId()){ case R.id.FileSelectListOK_Button: //現在のディレクトリをイベントで通知 if(getOnFileListListener() != null){ getOnFileListListener().onSelectDirectory(getCurrentDirectory()); } break; default: break; } } /** * 選択した時のリスナ用インターフェースクラス * @author Iori * */ public interface onFileListListener{ public void onSelectFile(File file); public void onSelectDirectory(File file); public void onChangeDirectory(File file); } }
カスタムビューで使用するリソース
レイアウト(file_list_view.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:id="@+id/FileListCurrentPath_TextView" android:textSize="18dip"> </TextView> <ListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/FileList_ListView" android:cacheColorHint="#00000000"> </ListView> </LinearLayout>
レイアウト(file_list_view_dir.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:id="@+id/FileListCurrentPath_TextView" android:textSize="18dip"> </TextView> <ListView android:layout_height="fill_parent" android:id="@+id/FileList_ListView" android:layout_width="fill_parent" android:layout_weight="1" android:cacheColorHint="#00000000"> </ListView> <Button android:id="@+id/FileSelectListOK_Button" android:layout_height="wrap_content" android:layout_width="fill_parent" android:text="@string/file_list_select_here"> </Button> </LinearLayout>
文字列リソース(strings_file_list_view.xml)
<!-- デフォルト values --> <?xml version="1.0" encoding="utf-8"?> <resources> <string name="file_list_select_here">Select here</string> <string name="file_list_dont_get_list">Cannot get list.</string> <string name="file_list_empty"><Empty></string> </resources> <!-- 日本語 values-ja --> <?xml version="1.0" encoding="utf-8"?> <resources> <string name="file_list_select_here">ここを選択する</string> <string name="file_list_dont_get_list">一覧を取得できません</string> </resources>
biochem_fan
こんにちは。
FileListDialog を使用させていただいております。
Android 3.2 (HoneyComb) 環境で、ダイアログが閉じるとき
(メニューボタンで戻るとき、キャンセルを選んだ時、ファイルを選択したとき)、
http://stackoverflow.com/questions/7290841/java-lang-illegalargumentexception-the-observer-is-null
と類似の例外で落ちるようです。
Android 内部の問題のような気もしますが、
ダイアログが破棄される前に、内部のリストビューに対して
setAdapter(null) することで解消することが分かりましたので
報告させていただきます。
あやね
> biochem_fanさん
報告ありがとうございます!