今回のポイント
・ディレクトリ選択モードとの使い分けができる
(モードは途中で切り替えできません)
・ファイルの一覧表示部分をカスタムビューとして作った
・戻るキーでディレクトリをさかのぼれる
(設定で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さん
報告ありがとうございます!