2020年11月19日木曜日

MaterialAlertDialogBuilder のボタンの色を変更する

non-Bridge なテーマで MaterialAlertDialogBuilder を使うと、ダイアログのボタンの色は colorPrimary になります。

そのため colorPrimary に黒っぽい色を指定した DayNight テーマだと、Dark Mode のときにボタンの文字が見えないという状態になってしまいます。 <resources> <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <item name="colorPrimary">#212121</item> </style> </resources>




DayNight テーマの colorPrimary は変えずに Dark Mode のときだけダイアログのボタンの色を変えるには、materialAlertDialogTheme 属性を指定します。

res/values/themes.xml <resources> <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <item name="colorPrimary">#212121</item> <item name="materialAlertDialogTheme">@style/ThemeOverlay.MyApp.MaterialAlertDialog</item> </style> <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog" /> </resources> res/values-night-v8/themes.xml <resources> <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <item name="colorPrimary">#ffffff</item> </style> </resources>




2020年11月12日木曜日

複数の nullable な mutable 変数の null チェック

nullable な mutable 変数 a と b があって、両方 non-null のとき何かしたいという場合、

これだとコンパイルエラーになる var a: String? = "Hello" var b: String? = "Android" fun main() { if (a != null && b != null) { println(a.length + b.length) // compile error! } } let だと入れ子になるからいまいち var a: String? = "Hello" var b: String? = "Android" fun main() { a?.let { a -> b?.let { b -> println(a.length + b.length) } } } ローカルの変数に入れれば OK var a: String? = "Hello" var b: String? = "Android" fun main() { val a = a val b = b if (a != null && b != null) { println(a.length + b.length) // ok } } vararg を使って、渡した値が全部 non-null だったら block を実行するというメソッドを用意すると、let っぽい感じで入れ子にせずに書ける var a: String? = "Hello" var b: String? = "Android" fun main() { doIfAllNotNull(a, b) { (a, b) -> println(a.length + b.length) } } private inline fun <T> doIfAllNotNull( vararg value: T?, block: (values: List<T>) -> Unit ) { val nonNullValues = value.filterNotNull() if (nonNullValues.size == value.size) { block(nonNullValues) } }

2020年11月11日水曜日

Kotlin メモ : vetoable

vetoable

変更を拒否(veto)するかどうかのコールバックを指定できる property delegate を返す。

fun main() { var value: Int by Delegates.vetoable(0) { property, oldValue, newValue -> newValue > 0 } println(value) // 0 value = 10 println(value) // 10 value = -1 println(value) // 10 }

2020年11月10日火曜日

ConstraintSet を使って Activity を再生成せずにレイアウトを切り替える

自分で縦横レイアウトを切り替えるので Manifest に設定を追加 <activity android:name=".MainActivity" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"> ... </activity> 縦画面用のレイアウトと、横画面用のレイアウトを用意する。
例としてここでは縦画面では上部に 16:9 で配置し、横画面では全画面に配置している。

縦画面用
res/layout/activity_main_port.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <include android:id="@+id/container" layout="@layout/activity_main" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintDimensionRatio="16:9" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 横画面用
res/layout/activity_main_land.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <include android:id="@+id/container" layout="@layout/activity_main" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> activity_main_port.xml と activity_main_land.xml からそれぞれ ConstraintSet を構成する。
onConfigurationChanged() で画面の向きに応じて対応する ConstraintSet を ConstraintLayout に applyTo() する。 class MainActivity : AppCompatActivity() { private val constraintSetPort = ConstraintSet() private val constraintSetLand = ConstraintSet() private lateinit var constraintLayout: ConstraintLayout override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val layoutId = when (resources.configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> R.layout.activity_main_land else -> R.layout.activity_main_port } setContentView(layoutId) constraintLayout = findViewById(R.id.constraintLayout) constraintSetPort.clone(this, R.layout.activity_main_port) constraintSetLand.clone(this, R.layout.activity_main_land) } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) val constraintSet = when (newConfig.orientation) { Configuration.ORIENTATION_LANDSCAPE -> constraintSetLand else -> constraintSetPort } constraintSet.applyTo(constraintLayout) } }





2020年11月5日木曜日

Android 11 では ACTION_CREATE_DOCUMENT と ACTION_OPEN_DOCUMENT には <queries> 指定が必要

Intent.ACTION_CREATE_DOCUMENT および Intent.ACTION_OPEN_DOCUMENT を startActivityForResult() で呼び出す以下のコードは、targetSdkVersion が 29 (Android 10)までは問題なく動きます。 private fun createDocument() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) .addCategory(Intent.CATEGORY_OPENABLE) .setType("*/*") .putExtra(Intent.EXTRA_TITLE, "log.txt") if (intent.resolveActivity(packageManager) != null) { startActivityForResult(intent, REQUEST_CODE_CREATE_DOCUMENT) } else { Toast.makeText( this, "Unable to resolve Intent.ACTION_CREATE_DOCUMENT", Toast.LENGTH_SHORT ) .show() } } private fun openDocument() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) .addCategory(Intent.CATEGORY_OPENABLE) .setType("*/*") if (intent.resolveActivity(packageManager) != null) { startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT) } else { Toast.makeText( this, "Unable to resolve Intent.ACTION_OPEN_DOCUMENT", Toast.LENGTH_SHORT ) .show() } } しかし、targetSdkVersion を 30 (Android 11)にすると、intent.resolveActivity(packageManager) が null を返すようになってしまいます。

Android 11 では AndroidManifest.xml に以下の <queries> を指定すると動くようになります。 <manifest ...> ... <queries> <intent> <action android:name="android.intent.action.CREATE_DOCUMENT" /> <data android:mimeType="*/*" /> </intent> <intent> <action android:name="android.intent.action.OPEN_DOCUMENT" /> <data android:mimeType="*/*" /> </intent> </queries> ... </manifest>