Android Binding 〜SDカードファイルビュー〜

昨日の続きです。今日は以下のことをやろうと思います。

  1. リストをソートして表示
  2. ファイルを選択したらIntentを投げてアプリ起動
  3. 現在のパスをタイトルバーに表示


サンデープログラミングなのでかなりヌルイ目標設定になっています (汗) 。一つずつ対応していきましょう。

まずもっとも簡単なソートです。これはViewModelクラス内でFile配列をDirectoryEntryクラスの配列に変換してArrayListObservableオブジェクトに設定しているところがあるのでここでソートしてしまいます。

◆ViewModelクラス

public void setPath(String path) {

    // ... 省略 ...

    // エントリ列を昇順にソートする (ココ!)
    Arrays.sort(entries, _entryComparator);

    // リスト表示を更新する
    DirectoryEntryList.setArray(entries);
}

// ディレクトリエントリ列ソート用比較オブジェクト
private final Comparator<DirectoryEntry> _entryComparator = new Comparator<TestExplorerViewModel.DirectoryEntry>() {
    @Override
    public int compare(DirectoryEntry entry1, DirectoryEntry entry2) {

        // まずはディレクトリを優先する
        boolean isFile1 = entry1.IsFile.get();
        boolean isFile2 = entry2.IsFile.get();
        if(!isFile1 && isFile2)
            return -1;
        if(isFile1 && !isFile2)
            return 1;

        // それでも決着が付かない場合は名前順とする
        return entry1.Name.get().compareTo(entry2.Name.get());
    }
};


ソートは次のような法則で行っています。

  1. ディレクトリ < ファイル
  2. エントリ名UNICODE昇順


ディレクトリとファイルを分けるとリストの見通しが若干よくなります。

次にファイルをタップしたらIntentを投げるようにしてみます。これでグッとエクスプローラ風に見えるようになります。こちらはリストクリックアクションにバインドされたコマンドを変更します。

◆ViewModelクラス

public Command OnItemClicked = new Command() {
    @Override
    public void Invoke(View view, Object... args) {

        // ... 省略 ...

        // ファイルをタップしたらIntent起動する
        if(clicked.IsFile.get() == true) {
            sendIntent(_currentPath + "/" + clicked.Name.get());
            return;
        }

        // ... 省略 ...
    }
}

private void sendIntent(String path) {

    // 暗黙Intentのためのパラメータを準備する
    File   file = new File(path);
    Uri    uri  = Uri.fromFile(file);
    String mime = MimeUtils.getMimeType(file.getName());

    // Intentオブジェクトを初期化する
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setDataAndType(uri, mime);

    // 暗黙Intentを発行する
    try {
        context.startActivity(intent);
    } catch(ActivityNotFoundException e) {
        Toast.makeText(
                _context,
                _context.getText(R.string.application_not_found),
                Toast.LENGTH_LONG).show();
    }
}


ここで注意が必要なのはIntentを発行したり、トーストメッセージを表示する時、Contextオブジェクトが必要だということです。こればっかりは外から (Activityから) もらわないとイケません。なので、ViewModelクラスのコンストラクタでもらうことにしました。

◆ViewModelクラス

private Context _context = null;

public TestExplorerViewModel(Context context) {
    _context = context;
}


最後の目標も片付けてしまいましょう。タイトルバーに現在のパスを表示します。

・・・と思い、頑張ってタイトルバーをカスタマイズしてパスを表示しようとしましたが、どうもタイトルバーからはバインド出来ないようです。

なので、タイトルを消して、ListViewの (固定) ヘッダを追加して、タイトルバーっぽく見せるようにしました。仕事でもそうだったんですが、タイトルバーのカスタマイズって何かとうまくいかない (ように感じます) 。

タイトルと現在パスをバインドするのはとっても簡単。

◆レイアウト定義

    <!-- ... 省略 ... -->
    <TextView
        ... 省略 ...
        binding:text="CurrentPath" />


◆ViewModelクラス

// 現在パスプロパティ
// これまで_currentPathに設定していたのをこちらに設定するようにする
public StringObservable CurrentPath = new StringObservable("");


これで終了です。この辺りの簡潔さがAndroid bindingらしいところですね。

これらの他にちょっと見た目を凝ってみました。アイコンがいまいちなのはご勘弁。


いつものようにソースコードはこちらです。ご参考ください。

サンプルコード置き場