Android4系の登場に伴って、ダイアログの表示に Activityクラスの showDialogメソッドを使用することは非推奨となり、ダイアログフラグメント(DialogFragmentクラス)を使用することとなりました。
最近の eclipseでの開発環境上では、サポートパッケージ(android-support-v4.jar)が標準添付するようになっています。このパッケージを利用することで、Android2~3系でも、ダイアログフラグメントを使用することが可能になっています。
Andoroidには、標準的なファイル選択ダイアログが用意されておらず、ネット上に手ごろなソースコードも見当たらなかったので自作してみました。
アラートダイアログ(AlertDialogクラス)のビューをリストビュー(ListView)にすることで実現しました。
package org.wisterina.android.utility.dialogfragment.fileselect;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* ファイルを選択するダイアログフラグメントクラスです。
*/
public class FileSelectDialogFragment extends DialogFragment implements AdapterView.OnItemClickListener {
/**
* ルートディレクトリを表します。
*/
public static final String ROOT_DIRECTORY = "rootDirectory";
/**
* ダイアログ表示直後のディレクトリを表します。
*/
public static final String INITIAL_DIRECTORY = "initialDirectory";
/**
* 親ディレクトリのテキストを表します。
*/
public static final String PREVIOUS = "previous";
/**
* キャンセルボタンの文字列を表します。
*/
public static final String CANCEL = "cancel";
/**
* リスナを表します。
*/
public static final String LISTENER = "listener";
/**
* 現在選択中のディレクトリを表します。
*/
private static final String DIRECTORY = "directory";
/**
* アダプター。
*/
private ArrayAdapter<String> adapter;
/**
* ファイル情報。
*/
private List<FileInformation> fileInformations = new ArrayList<FileInformation>();
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Activity activity = this.getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
ListView listView = new ListView(activity);
this.adapter = new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1);
listView.setAdapter(this.adapter);
this.initializeArguments();
// ビューを更新する
this.updateView();
listView.setOnItemClickListener(this);
builder.setView(listView);
Bundle bundle = this.getArguments();
builder.setTitle(bundle.getString(DIRECTORY) + File.separator);
builder.setNegativeButton(bundle.getString(CANCEL), new CancelListener());
return builder.create();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Dialog dialog = this.getDialog();
Bundle bundle = this.getArguments();
String directory = bundle.getString(DIRECTORY);
if(position == 0) {
// 一番上を選択した場合
if(directory.length() > bundle.getString(ROOT_DIRECTORY).length()) {
// 通常は戻る処理をする
directory = directory.substring(0, directory.lastIndexOf(File.separator));
bundle.putString(DIRECTORY, directory);
// ダイアログとビューを更新
dialog.setTitle(directory + File.separator);
this.updateView();
} else {
// トップディレクトリの場合は何もしない
}
} else {
directory = directory + File.separator + this.fileInformations.get(position - 1).getFile().getName();
File file = new File(directory);
if(file.isDirectory()) {
// ディレクトリの場合はその中へ移動
bundle.putString(DIRECTORY, directory);
// ダイアログとビューを更新
dialog.setTitle(directory + File.separator);
this.updateView();
} else {
// ファイルが確定
OnFileSelectedListener listener = (OnFileSelectedListener)this.getArguments().getSerializable(LISTENER);
listener.onFileSelected(directory);
// このダイアログを終了
this.dismiss();
}
}
}
/**
* 要素情報を初期化します。
*/
private void initializeArguments() {
Bundle bundle = this.getArguments();
if(bundle.getString(ROOT_DIRECTORY) == null) {
bundle.putString(ROOT_DIRECTORY, File.separator);
}
if(bundle.getString(INITIAL_DIRECTORY) == null) {
bundle.putString(INITIAL_DIRECTORY, bundle.getString(ROOT_DIRECTORY));
}
if(bundle.getString(DIRECTORY) == null) {
bundle.putString(DIRECTORY, bundle.getString(INITIAL_DIRECTORY));
}
if(bundle.getString(PREVIOUS) == null) {
bundle.putString(PREVIOUS, "..");
}
}
/**
* ビューを更新します。
*/
private void updateView() {
this.adapter.clear();
Bundle bundle = this.getArguments();
this.adapter.add(bundle.getString(PREVIOUS));
String directory = bundle.getString(DIRECTORY);
if(directory.equals("")) {
directory = File.separator;
}
this.fileInformations.clear();
File[] files = new File(directory).listFiles();
if(files != null) {
for(File file : files) {
this.fileInformations.add(new FileInformation(file));
}
// ソート
Collections.sort(this.fileInformations);
for(FileInformation fileInformation : this.fileInformations) {
File file = fileInformation.getFile();
if(file.isDirectory()) {
this.adapter.add(file.getName() + File.separator);
} else {
this.adapter.add(file.getName());
}
}
}
}
/**
* キャンセル処理をするリスナです。
*/
private class CancelListener implements DialogInterface.OnClickListener {
@Override
public void onClick(DialogInterface dialog, int which) {
OnFileSelectedListener listener = (OnFileSelectedListener)FileSelectDialogFragment.this.getArguments().getSerializable(LISTENER);
listener.onFileSelectCanceled();
}
}
}
package org.wisterina.android.tool.dialogfragment.fileselect;
import java.io.Serializable;
/**
* ファイル選択のリスナーです。
*/
public interface OnFileSelectedListener extends Serializable {
/**
* ファイルが選択された時に呼び出されます。
* @param path パス
*/
public abstract void onFileSelected(String path);
/**
* ファイル選択がキャンセルされたときに呼び出されます。
*/
public abstract void onFileSelectCanceled();
}
package org.wisterina.android.utility.dialogfragment.fileselect;
import java.io.File;
import java.util.Locale;
/**
* ファイル情報を扱うクラスです。パッケージ内クラス。
*/
class FileInformation implements Comparable<FileInformation> {
/**
* ファイル。
*/
private File file;
/**
* コンストラクタ。
* @param file ファイル
*/
public FileInformation(File file) {
this.file = file;
}
@Override
public int compareTo(FileInformation opponent) {
if(this.file.isDirectory() && ! opponent.file.isDirectory()) {
return -1;
} else if(! this.file.isDirectory() && opponent.file.isDirectory()) {
return 1;
} else {
return this.file.getName().toLowerCase(Locale.US).compareTo(opponent.file.getName().toLowerCase(Locale.US));
}
}
/**
* ファイルを返します。
* @return ファイル
*/
public File getFile() {
return this.file;
}
}
こんな感じで使用します。
package org.wisterina.android.fileselectdialog;
import org.wisterina.android.utility.dialogfragment.fileselect.FileSelectDialogFragment;
import org.wisterina.android.utility.dialogfragment.fileselect.OnFileSelectedListener;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends FragmentActivity implements OnFileSelectedListener, View.OnClickListener {
/**
* デフォルトシリアルバージョン ID。
*/
private static final long serialVersionUID = 1L;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.findViewById(R.id.button1).setOnClickListener(this);
}
@Override
public void onClick(View view) {
// ダイアログフラグメントを設定
DialogFragment dialogFragment = new FileSelectDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(FileSelectDialogFragment.ROOT_DIRECTORY, "/");
bundle.putString(FileSelectDialogFragment.INITIAL_DIRECTORY, Environment.getExternalStorageDirectory().getPath());
bundle.putString(FileSelectDialogFragment.PREVIOUS, "..");
bundle.putString(FileSelectDialogFragment.CANCEL, "キャンセル");
bundle.putSerializable(FileSelectDialogFragment.LISTENER, this);
dialogFragment.setArguments(bundle);
dialogFragment.setCancelable(false);
dialogFragment.show(this.getSupportFragmentManager(), "dialog");
}
@Override
public void onFileSelected(String path) {
Toast.makeText(this, path, Toast.LENGTH_LONG).show();
}
@Override
public void onFileSelectCanceled() {
Toast.makeText(this, "キャンセル", Toast.LENGTH_SHORT).show();
}
}
文字列のリソース化や、外部ストレージのマウントのチェックなどの処理を省略しています。