はじめに
AlertDialogの見た目はAlertDialog.Builderの下記のAPIで調整できます。しかし、どうしても謎のpaddingやSpaceに邪魔をされることがあったので、その解決方法を記載します。
- Builder() : ダイアログ全体のstyleが指定できる。
- setCustomTitle() : タイトルのlayoutを指定できる。backgroundを変更したい時などに使える。
- setView() : コンテンツ領域のlayoutを指定できる。
実例
Android 6 で下記のようにAlertDialogを作成すると、下線付きタイトルと縦長のメッセージが表示されますが、画像の赤丸のように謎のpaddingが入ってしまっています。
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(context)
.setCustomTitle(activity!!.layoutInflater.inflate(R.layout.layout_dialog_title, null))
.setMessage((0..100).joinToString("\n") { i -> "hello ${i}" })
.setPositiveButton("OK", null)
.create()
}
原因の調査
まず、AlertDialogのレイアウトの構造を調べるために先ほどのコードを以下のように書き換え実行し、Logcatを確認します。
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(context)
.setCustomTitle(activity!!.layoutInflater.inflate(R.layout.layout_dialog_title, null))
.setMessage((0..100).joinToString("\n") { i -> "hello ${i}" })
.setPositiveButton("OK", null)
.create()
.also { dialog ->
dialog.show() // show() しておかないとViewが取得できない
showViewInfoRecursively(dialog.window.decorView)
}
}
private fun showViewInfoRecursively(
view: View?,
hierarchy: Int = 0
) {
if (view == null) return
val indent = (0..hierarchy).joinToString(" ") { "" }
val resourceName =
if (view.id != View.NO_ID) view.resources.getResourceName(view.id)
else "none"
Log.d("CustomDialog", "${indent}class=${view.javaClass.simpleName}, resource=${resourceName}, id=${view.id}")
if (view is ViewGroup)
for (i in 0..view.childCount)
showViewInfoRecursively(view.getChildAt(i), hierarchy + 1)
}
D/CustomDialog: class=DecorView, resource=none, id=-1
D/CustomDialog: class=FrameLayout, resource=none, id=-1
D/CustomDialog: class=FrameLayout, resource=android:id/content, id=16908290
D/CustomDialog: class=LinearLayout, resource=android:id/parentPanel, id=16909076
D/CustomDialog: class=LinearLayout, resource=android:id/topPanel, id=16909077
D/CustomDialog: class=ConstraintLayout, resource=none, id=-1
D/CustomDialog: class=AppCompatTextView, resource=none, id=-1
D/CustomDialog: class=LinearLayout, resource=android:id/title_template, id=16909078
D/CustomDialog: class=AppCompatImageView, resource=android:id/icon, id=16908294
D/CustomDialog: class=DialogTitle, resource=android:id/alertTitle, id=16909079
D/CustomDialog: class=FrameLayout, resource=android:id/contentPanel, id=16909084
D/CustomDialog: class=ScrollView, resource=android:id/scrollView, id=16909085
D/CustomDialog: class=LinearLayout, resource=none, id=-1
D/CustomDialog: class=AppCompatTextView, resource=android:id/message, id=16908299
D/CustomDialog: class=Space, resource=android:id/textSpacerNoButtons, id=16909090
D/CustomDialog: class=FrameLayout, resource=android:id/customPanel, id=16909086
D/CustomDialog: class=FrameLayout, resource=android:id/custom, id=16908331
D/CustomDialog: class=ButtonBarLayout, resource=android:id/buttonPanel, id=16909081
D/CustomDialog: class=AppCompatButton, resource=android:id/button3, id=16908315
D/CustomDialog: class=Space, resource=android:id/spacer, id=16909087
D/CustomDialog: class=AppCompatButton, resource=android:id/button2, id=16908314
D/CustomDialog: class=AppCompatButton, resource=android:id/button1, id=16908313
D/CustomDialog: class=ViewStub, resource=android:id/action_mode_bar_stub, id=16909303
Logcatに表示されたレイアウト構造から怪しげなViewを探します。ここでは、Spaceクラスが見当たらないので、いづれかのViewのmarginかpaddingが原因だと思われます。ここではリソース名がscrollViewのScroolViewとその子のLinearLayoutに当たりをつけます。
原因の確認
さらに以下のようにコードを変更し実行後、ScrollViewの境界(赤)とLinearLayoutの境界(青)を表示してみます。画像を見ると、問題の空白が赤線と青線で挟まれているので、原因はScrollViewのpaddingTopかLinearLayoutのlayoutMarginTopです。
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(context)
.setCustomTitle(activity!!.layoutInflater.inflate(R.layout.layout_dialog_title, null))
.setMessage((0..100).joinToString("\n") { i -> "hello ${i}" })
.setPositiveButton("OK", null)
.create()
.also { dialog ->
dialog.show() // show() しておかないとViewが取得できない
// showViewInfoRecursively(dialog.window.decorView)
// 公開されていないリソース名でViewを探す
dialog.findViewById<ScrollView?>(getResources().getIdentifier(
"scrollView", "id", "android"
))?.also { scrollView ->
// ScrollView に赤いフレームを付与
scrollView.setBackgroundResource(R.drawable.shape_frame_red)
// ScrollView の子のLinearLayoutに青いフレームを付与
scrollView.getChildAt(0).setBackgroundResource(R.drawable.shape_frame_blue)
}
}
}
修正
最終的な修正コードは以下のようになります。実行すると画像のように空白がなくなっています。
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(context)
.setCustomTitle(activity!!.layoutInflater.inflate(R.layout.layout_dialog_title, null))
.setMessage((0..100).joinToString("\n") { i -> "hello ${i}" })
.setPositiveButton("OK", null)
.create()
.also { dialog ->
dialog.show() // show() しておかないとViewが取得できない
// showViewInfoRecursively(dialog.window.decorView)
// 公開されていないリソース名でViewを探す
dialog.findViewById<ScrollView?>(getResources().getIdentifier(
"scrollView", "id", "android"
))?.also { scrollView ->
// 今回の原因. paddingTopに24が設定されていた.
Log.d("CustomDialog", "paddingTop=${scrollView.paddingTop}")
scrollView.setPadding(0, 0, 0, 0)
// 今回の原因ではないが修正方法を記載
// val child = scrollView.getChildAt(0)
// child.layoutParams = FrameLayout.LayoutParams(child.layoutParams)
// .also { it.setMargins(0, 0, 0, 0) }
}
}
}
おわりに
SDKが意図していない方法でのレイアウト編集なので、SDKのバージョンが変われば動かなくなるかもしれません。あくまでも最終手段となります。