操作性ユーザ体感の改善の中でダイアログやデータベース以外の保存機能を学習します。
蔵書詳細画面作成からの引き続きの学習ページです。
学習ポイント
- DialogFragment
- SharedPreference
- プロジェクトのパッケージ整理
Androidアプリでデータ通信中にユーザ操作を受けずにユーザにアプリの状況を伝えるためにダイアログ表示の方法を学習します。
また、アプリ内でデータベース以外にデータを保持する方法としてSharedPreferenceの機能を学習します。
ユーザ体験の改善
前ページまでに実装してきたアプリの中で、データ読み込み中に画面が真っ白になってしまい読み込み時間が長くなるとアプリが止まってしまったと感じてアプリを終了するユーザがいるかもしれません。
そこで、アプリが動いていることを通知するためにダイアログ表示を行うように処理を実装していきます。
データ読み込み中のダイアログ表示機能
Androidアプリでダイアログを表示するためにはDialogFragment
クラスを継承したカスタムクラスを作成し、作成したカスタムクラスにどういった内容でダイアログ表示を行うか実装して行く。
カスタムDialogFragment作成
以下の手順から新規javaクラスを作成します。
New -> Fragment -> Fragment(Blank)
項目 | 設定値 |
---|---|
Fragment Name | ProgressDialogFragment |
Create Layout XML? | チェックをつける |
Fragment Layout Name | fragment_progress_dialog |
Include fragment factory methods? | チェックをつけない |
Include interface callbacks? | チェックをつけない |
Source Language | Java |
ダイアログ表示用のFragmentを作成したらレイアウトXMLを修正していきます。
LayoutEditor左下からTextタブをクリックしテキストエディタを表示し、以下の修正通り修正します。
以下レイアウトファイルを表示します。
app -> res -> layout -> fragment_progress_dialog.xml
ここでテキストエディタモードでレイアウトを編集するとき、画面右端に表示されているPreviewをクリックすると、リアルタイムにプレビューを表示されるので利用してみてください。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46<!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓削除↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="kuririnz.xyz.bookdiscovery.ProgressDialogFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑削除↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<ProgressBar
android:id="@+id/ProgressCircle"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large"
android:layout_margin="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/ProgressCircle"
android:layout_centerVertical="true"
android:layout_margin="10dp"
android:text="書籍情報を検索中..."
android:textSize="15dp"
android:textColor="@color/colorAccent"
android:textAlignment="center"/>
</RelativeLayout>
</RelativeLayout>
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
ProgressDialogFragment
が作成できたらダイアログ表示用のFragmentにするため、親クラスをDialogFramgnetに変更します。
このDialogFragmentへの変更で気をつけて欲しいのがインポートされるパッケージです。
以下のパッケージが追加されていることを確認してください。
import android.support.v4.app.DialogFragment;
1 | //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ |
1 | public class ResultListFragment extends Fragment implements AdapterView.OnItemClickListener{ |
上記実装が終わったら動作確認してください。
検索結果画面へ画面遷移した際に半透過に一部白いウィンドウのダイアログが表示されると思います。
DialogとAlertDialog
DialogFragment
クラスはDialogクラスのオブジェクトを保持しており、Dialogオブジェクトに生成したレイアウトを表示させます。
またDialogには利用方法によって利用するクラスが2種類ありますそれがDialogとAlertDialogです。
Dialogは開発者が指定したレイアウトを表示するだけで、ユーザへの情報を表示するために利用することが多いと思われます。
AlertDialogは開発者が指定したレイアウトの他に”OK”,”Cancel”などのボタンがメインレイアウトとは別に準備されており、ユーザに応答を求めるときや確認のダイアログとして利用することが多いです。
読み込みキャンセル機能
先ほどの実装では検索結果が表示されるまでアプリユーザは何もすることができませんでしたが、読み込み中でも検索画面に戻れるようにAlertDialog
を使用してキャンセルボタンをダイアログに追加します。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class ProgressDialogFragment extends DialogFragment {
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().setCancelable(false);
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_progress_dialog, container, false);
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
public Dialog onCreateDialog(Bundle savedInstanceState) {
// ダイアログ表示するレイアウトを生成
View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_progress_dialog, null, false);
// アラートダイアログビルダーを使ってボタン付きのダイアログを生成
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setView(view)
.setPositiveButton("キャンセル", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
getActivity().finish();
}
})
.setCancelable(false);
// 表示するダイアログを生成して返却
return builder.create();
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
1 | // 検索結果をListViewに反映するメインスレッドの処理クラス |
上記コードが実装できたら動作確認してください。
読み込み中のダイアログにキャンセルボタンが追加され、キャンセルボタンを押下すると検索画面に戻れるようになっているはずです。
DialogFragmentのライフサイクル
DialogFragment
を使用してダイアログ表示を行う場合に、ライフサイクルが通常のFragmentクラスと少し違うため、注意が必要です。
getLayoutInflater -> onCreateDialog -> onCreateView -> onActivityCreated
最後に検索した文字列を保持する機能
以前にアプリが終了されてもデータが残る機能としてデータベース(Realm)を紹介しましたが、
データの量が少ない場合などに手軽に使える機能をAndroidは備えており、容易に使えます。
今回はSharedPreference
という機能を使い検索した文言を保持する機能を実装していきます。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class MainActivity extends AppCompatActivity {
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 最後の検索文言を保持/取得するキー定数
private final static String PREF_KEY = "LAST_TERM";
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// レイアウトxmlと関連付けるWidget
private Button bookSearchBtn;
private Button historyBtn;
...一部省略
View.OnClickListener bookSearchEvent = new View.OnClickListener() {
public void onClick(View view) {
...一部省略
} finally {
// Realmインスタンスがちゃんとクローズされること
realm.close();
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// SharedPreferenceに保存
SharedPreferences.Editor editor = MainActivity.this.getPreferences(Context.MODE_PRIVATE).edit();
editor.putString(PREF_KEY, termString).apply();
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// 検索結果画面へ遷移するためのIntentをインスタンス化
Intent intent = new Intent(MainActivity.this, ResultListActivity.class);
// EditTextに入力された文字列を"KeyValuePair"でResultListActivityに渡す
intent.putExtra("terms", termString);
// 画面遷移アクションを実行
startActivity(intent);
}
};
// 蔵書検索ボタンが押された時に実行するプログラムをボタンに登録
bookSearchBtn.setOnClickListener(bookSearchEvent);
// 検索履歴ボタンをクリックした時の処理を実装
historyBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
// 検索履歴画面へ遷移するためのIntentをインスタンス化
Intent intent = new Intent(MainActivity.this, HistoryActivity.class);
// 画面遷移アクションを実行
startActivity(intent);
}
});
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓追加↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// SharedPreferenceに最後の検索条件が残っていたら登録する
// SharedPreferenceにデータがない場合は空文字を設定する
String lastTerm = this.getPreferences(Context.MODE_PRIVATE).getString(PREF_KEY, "");
bookSearchEditor.setText(lastTerm);
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑追加↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
...一部省略
}
上記コードを実装したら動作確認してください。
初回起動時のEditTextは空文字(何も表示されてない)となっており、何かしらの文言で検索を行いアプリを終了させます。
改めてアプリを起動したときにEditTextに検索した文言が表示されていることが確認できます。
Android端末のアプリ専用領域に環境設定としてKey-Value形式でデータを保存/参照できる機能。
SharedPreferenceのインスタンス化の引数ではインスタンス化するSharedPreferenceのアクセス許容範囲を指定できます。
今回はContext.MODE_PRIVATE
ということで自アプリからしかアクセスできないSharedPreferenceを作成し、その中に”LAST_TERM”という鍵[Key]情報に[最後に検索した文字列]というデータ(Value)を保存しました。
JSONの概念が理解できている方は{"LAST_TERM":$term}
と保存されている認識で良いと思います。
またSharedPreferenceは名前をつけて複数作成することができ、JSONファイルを作成してJSON形式でデータを保持していると思っていただいても良いと思います。
簡単に利用できるSharedPreferenceですが、起動時にメモリ上に読み込む仕様になっているため、大量のSharedPreferenceやデータの保持はアプリの挙動に影響を与える可能性があるので少量で使うようにすることをお勧めします。
Androidプロジェクトの整理
ここに至るまでに複数のActivityやFragmentを作成し、類似するネーミングのものも存在するため、ツリー表示が見にくく感じます。
そこで作成したファイルごとにパッケージを分けて保存することで探しやすさと視認性をよくしていきます。
このときにマウス操作でドラッグ&ドロップを行うとjavaクラスファイルの格納場所を示すPackage(パッケージ)情報に不整合が発生してしまい、
修正が大変になることがあります。
Package情報の整合性を保ちつつjavaクラスの格納場所を変更するにはAndroid StudioのRefactor -> Move
機能を使って格納場所を変更します。
方法としてはまず、分類分けするためのPackageを追加します、このPackageはフォルダとして実態が生成されます。
プロジェクトツリーで右クリックから以下を選択
New -> Package
続いて作成するPackage名を入力し、OK
をクリック
分類用のPackageが作成されたらプロジェクトツリーで右クリックから以下を選択
Refactor -> Mode…
次に移動先を選択するために”To Package”列の...
ボタンをクリック
先ほど作成したbookdiscovery.Activity
を選択し、OK
をクリックbookdiscovery.Activity
が選択されていることを確認し、Refactor
をクリック
プロジェクトツリー表示を確認するとHistoryActivity.java
が…*.bookdiscovery.Activity配下に移動します
このときにHistoryActivity.java
を参照しているMainActivity.java
やAndroidManifest.xml
ファイルも変更が加わっています。
また作業中に以下のようなメッセージが下部のツールエリアに表示されることがありますが、これは置換箇所や深い階層での参照が見つかった場合などに表示されることがあります。
置換箇所のプレビューになるのでないように問題がなければDo Refactor
をクリックします
以上の手順でPackageの分割や不整合のないファイル移動を行います。
今回は以下の通りに格納されるようにjavaクラスファイルを移動させて見てください
以上で、操作性・ユーザ体験の改善に関する解説は終了です。
次の蔵書詳細の元ウェブページの表示ではWebViewや他アプリへの連携などを学習します。