2018年9月15日土曜日

AlertDialog の Button の有効/無効を切り替える

AlertDialog の PositiveButton, NegativeButton, NeutralButton では、listener での実装によらずタップしたときに必ずダイアログが閉じます。

例えば AlertDialog でテキストを入力するようにして、未入力のときはボタンを押せないようにしたいとします。

AlertDialog の getButton() で Button インスタンスが取れるのでこれを利用します。
取得するボタンは BUTTON_POSITIVE, BUTTON_NEGATIVE, BUTTON_NEUTRAL で指定します。

あとは EditText に TextWatcher を追加して、テキストの変更時にボタンの isEnabled を変更します。

初回時 EditText が空ならボタンを disabled にしておかないといけません。
AlertDialog の getButton() は show() の前に呼ぶと NPE になるので注意が必要です。 val editText = EditText(this).apply { inputType = InputType.TYPE_CLASS_TEXT layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT ).apply { val margin = (16 * resources.displayMetrics.density).toInt() marginStart = margin marginEnd = margin } } val frameLayout = FrameLayout(this).apply { addView(editText) } val dialog = AlertDialog.Builder(this) .setTitle("Title") .setMessage("Message") .setView(frameLayout) .setPositiveButton(android.R.string.ok, null) .create() editText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = !s.isNullOrBlank() } }) dialog.show() dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false



Android Activity Transitions の xml 定義で exclude を指定する

コードでのやりかたは「Android Activity Transitions の対象から、Navigation Bar と Status Bar を外す(Activity Transitions を実装する その2)」に書きました。

xml で transition を定義する場合は以下のように targets タグと target タグを使います。 <?xml version="1.0" encoding="utf-8"?> <slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="end"> <targets> <target android:excludeId="@android:id/navigationBarBackground" /> <target android:excludeId="@android:id/statusBarBackground" /> </targets> </slide> target タグには excludeId の他にも以下の属性を指定できます。
  • android:targetClass
  • android:targetId
  • android:excludeId
  • android:excludeClass
  • android:targetName
  • android:excludeName


2018年9月14日金曜日

android:windowCloseOnTouchOutside を指定するとどうなるのか

android:windowCloseOnTouchOutside は API Level 11 で追加されたテーマ用の属性で、true を指定すると、Dialog 系の theme を指定した Activity でダイアログ(というか window)以外の領域をタップしたときにダイアログが閉じます(というか Activity が finish() します)。

この属性は Window に関するもので、Window では以下のフィールドとメソッドが関連します。

Window public abstract class Window { ... private boolean mCloseOnTouchOutside = false; private boolean mSetCloseOnTouchOutside = false; ... /** @hide */ public void setCloseOnTouchOutside(boolean close) { mCloseOnTouchOutside = close; mSetCloseOnTouchOutside = true; } /** @hide */ public void setCloseOnTouchOutsideIfNotSet(boolean close) { if (!mSetCloseOnTouchOutside) { mCloseOnTouchOutside = close; mSetCloseOnTouchOutside = true; } } ... /** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { final boolean isOutside = event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) || event.getAction() == MotionEvent.ACTION_OUTSIDE; if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) { return true; } return false; } private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth()+slop)) || (y > (decorView.getHeight()+slop)); } } Window の shouldCloseOnTouch() では、mCloseOnTouchOutside が true、かつ peekDecorView が null ではない、かつ MotionEvent の action が MotionEvent.ACTION_OUTSIDE、もしくは MotionEvent.ACTION_DOWN でタップ位置が DecorView の外側の場合、true が返ります。

Window の setCloseOnTouchOutside() は hide になっていて通常のアプリからは呼べません。 ではコードでは指定できないのかというと、Activity の setFinishOnTouchOutside() から指定できます。

Activity public void setFinishOnTouchOutside(boolean finish) { mWindow.setCloseOnTouchOutside(finish); } Activity の onTouchEvent() で Window の shouldCloseOnTouch() を呼んでおり、これにより DecorView の外側をタップすると Activity が finish() します。

Activity public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }

ちなみに android:windowCloseOnTouchOutside 属性の設定値は、PhoneWindow から Window.setCloseOnTouchOutsideIfNotSet() を呼ぶことで適用されています。