Gumma_de_Sharoushiのブログ

群馬で社労士として開業をめざします。→開業しました。

DialogのOKボタンを不活性化する方法【Android】

はじめてのAndroidアプリ開発第3版Android Studio 3対応 山田祥寛著(第2刷)

という本でAndroidアプリについて勉強しています。

 

その中で、うまくいかない現象がありましたので、対応方法とあわせて紹介します。

 

 

うまくいかなかった現象

チェックボックス式リストのDialogのところです(第6章)。

 

f:id:Gumma_de_Sharoushi:20220301180624p:plain

ダイアログチェックボックスの画面1

ボタンを押すと、チェックボックス式のダイアログが開きます。

f:id:Gumma_de_Sharoushi:20220301180802p:plain

チェックボックスが付いたダイアログ

全部のチェックを外してOKを押しますと、

 

f:id:Gumma_de_Sharoushi:20220301180901p:plain

全部のチェックを外してOK

アプリが落ちます。

f:id:Gumma_de_Sharoushi:20220301181001p:plain

アプリが落ちたところ

どうして落ちたかというと、↓下記msg.length()のところが0になり、0から-1しますので、当然msg.substring()がindex out of rangeとなるからです。

 

//[OK] ボタンを準備(現在の選択状態をトースト表示)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String msg = "";
for (int i = 0; i < selected.length; i++) {
if (selected[i]) {
msg += items[i] + ",";
}
}
Toast.makeText(getActivity(),
String.format("%s」が選択されました。",
msg.substring(0, msg.length() - 1)),
Toast.LENGTH_SHORT).show();
}
}
)
.create();

 

ダサい修正案

で、私はこういう風に直したのですが、

if(msg.isEmpty()){
//msgが空なら何もしない'
}else{
Toast.makeText(getActivity(),
String.format("%s」が選択されました。",
msg.substring(0, msg.length() - 1)),
Toast.LENGTH_SHORT).show();
}

なんか、ダサいですね・・・( ˘•ω•˘ ) 

 

不活性化を模索

そもそもチェックボックスが一つも選択されていないときにOKボタンが押せてしまうことがおかしいのであって、OKボタンを不活性化する方法がないか、探すことにしました。

 

Google先生に聞いたところ

  • DialogのgetButtonでボタンを取得できる。
  • ボタンに対してsetEnabled(false)で不活性化できる。
  • 活性化させたいときはsetEnabled(true)にすればよい。
  • setEnabledはDialogがshow()した後でないと使えない。

ということが分かりました。

 

でも現状のコーディングのどこに不活性化の手続きを入れたらよいのでしょう??

 

なかなかよい修正案

分からなくて悩んでいたら女神さま(比喩)がご降臨なさり、次のように教えてくれました。

 

修正前

package com.example.dialogcheckbox;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;

public class MyDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
//リスト項目とデフォルト値を準備
final String[] items = {
"電車","バス","徒歩","イカ","自転車","その他"};
final boolean[] selected = {true,true,true,false,false,false};
//ダイアログを生成
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
return builder.setTitle("通勤手段")
.setIcon(R.drawable.wings)
//チェックボックス式のリストを準備
.setMultiChoiceItems(items, selected,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog,
int which, boolean isChecked) {
selected[which] = isChecked;
}
}
)
//[OK] ボタンを準備(現在の選択状態をトースト表示)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String msg = "";
for (int i = 0; i < selected.length; i++) {
if (selected[i]) {
msg += items[i] + ",";
}
}
if(msg.isEmpty()){
//msgが空なら何もしない'
}else{
Toast.makeText(getActivity(),
String.format("%s」が選択されました。",
msg.substring(0, msg.length() - 1)),
Toast.LENGTH_SHORT).show();
}
}
}
)
.create();
}
}

 

修正後

package com.example.dialogcheckbox;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;

public class MyDialogFragment extends DialogFragment {
private AlertDialog dialog;
final private boolean[] selected = {true,true,true,false,false,false};
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
//リスト項目とデフォルト値を準備
final String[] items = {
"電車","バス","徒歩","イカ","自転車","その他"};
//ダイアログを生成
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
dialog= builder.setTitle("通勤手段")
.setIcon(R.drawable.wings)
//チェックボックス式のリストを準備
.setMultiChoiceItems(items, selected,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog,
int which, boolean isChecked) {
selected[which] = isChecked;
setEnableOK();
}
}
)
//[OK] ボタンを準備(現在の選択状態をトースト表示)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String msg = "";
for (int i = 0; i < selected.length; i++) {
if (selected[i]) {
msg += items[i] + ",";
}
}
if(!msg.isEmpty()){
Toast.makeText(getActivity(),
String.format("%s」が選択されました。",
msg.substring(0, msg.length() - 1)),
Toast.LENGTH_SHORT).show();
}
}
}
)
.create();
return dialog;
}

private void setEnableOK(){
for (boolean checked :selected) {
if(checked){
dialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(true);
break;
}
dialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(false);
}
}
}

 

setEnableOKというメソッドをprivateで作り、そこでgetButtonしてsetEnabledしています。

 

外にメソッドを設けたので、dialogをクラス変数にして、あわせてselectedもメソッドの外に出してクラス変数にしました。

 

setEnabledOKの呼び出し元は、setMultiChoiceItemsのクリックリスナーの中のonClickに置きます。

 

これで、不活性化ができるようになりました。

f:id:Gumma_de_Sharoushi:20220301190802p:plain

OKボタンが不活性化した状態


チェックを一つでもすると、OKボタンは活性化します。

f:id:Gumma_de_Sharoushi:20220301190849p:plain

OKボタンが活性化

 

キャンセルボタン

しかし、ここで新たな問題が。

 

不活性化した状態のときに、「やっぱやーめた」となったときに、キャンセルすることができません。

 

 

 

そういう画面遷移をさせないという仕様もあるでしょうが、ここはやはりキャンセルボタンを設けておくのが親切というものです(ダイアログのどこか外をクリックすれば元の画面に戻りますが、それもユーザに対して不親切)。

 

女神さまのコーディングの場合は、キャンセルボタンが用意されていました。

f:id:Gumma_de_Sharoushi:20220301192029p:plain

キャンセルボタンつき

コードはこんな感じです。(部分だけ)

.setNeutralButton("キャンセル",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
})
.create();

 

.create()の前あたりに.setNeutralButtonを入れます。

 

onClickの中身は空っぽでもよいようです。

 

今回、この件を調べた際、有益な情報をくれるサイトはいっぱいありましたが、どれもリスナー形式でなく、XMLのonClick特性にメソッドを記述するやり方が多かった(テキストとしている本もそのやり方でした)ので、リスナー形式の場合のやり方を紹介してみました。

 

ラムダ式の場合も同様になると思いますが、試していません><