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(); } }
文字列のリソース化や、外部ストレージのマウントのチェックなどの処理を省略しています。