Material Design Dialog
Well, it’s a matter of fact that good looking app should have a better dialog also whereas the current traditional alert dialog is quite old fashioned, so what can be done to make alert dialog looks good?
Using a third party library or making your own material theme alert dialog are your two options. If I have to choose, I would create them rather than depending on a third party as this is not something where you reinvent the wheel.
Let’s add the dependency
Add the material library (design library if you are not using AndroidX)
com.google.android.material:material:1.0.0
That’s it and we are ready to go.
One important thing to remember is that don’t forget to add material component theme as a parent for your app theme for example
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorControlActivated">?colorPrimary</item>
<item name="android:windowBackground">@color/home_screen_bg</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
<item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:textColorTertiary">@color/secondaryColorPrimary</item>
</style>
Create custom alert styles
Add the alert theme style in styles.xml
<style name="Theme.Tasker.Dialog" parent="@style/Theme.MaterialComponents.Light.Dialog">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:clickable">true</item>
<item name="android:textViewStyle">@style/Widget.Tasker.TextView</item>
</style>------------------------------------------------------------------
<style name="Widget.Tasker.TextView" parent="@android:style/Widget.Material.TextView">
<item name="android:textAppearance">@style/TextAppearance.Tasker.Body</item>
<item name="android:lineSpacingMultiplier">1.5</item>
</style>------------------------------------------------------------------<style name="TextAppearance.Tasker.Body" parent="@style/TextAppearance.AppCompat.Body1">
<item name="android:textColor">?android:textColorSecondary</item>
</style>
Now add theme for alert dialog content like title and body
<style name="TextAppearance.Tasker.DialogTitle" parent="@style/TextAppearance.AppCompat.Title">
<item name="android:textSize">22sp</item>
<item name="android:fontFamily">@font/google_sans</item>
<item name="android:gravity">start</item>
</style>
<style name="TextAppearance.Tasker.DialogContent" parent="@style/TextAppearance.AppCompat.Body1">
<item name="android:textSize">16sp</item>
<item name="android:textColor">?android:textColorSecondary</item>
<item name="android:gravity">start</item>
</style>
Finally theme for the buttons of material alert dialog
<style name="Widget.Tasker.BorderlessButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
<item name="android:fontFamily">@font/google_sans</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?colorPrimary</item>
<item name="android:letterSpacing">0.025</item>
<item name="android:textAllCaps">false</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
<item name="android:paddingTop">@dimen/padding_medium</item>
<item name="android:paddingBottom">@dimen/padding_medium</item>
</style>
<style name="Widget.Tasker.RaisedButton" parent="@style/Widget.AppCompat.Button.Colored">
<item name="android:fontFamily">@font/google_sans</item>
<item name="android:textStyle">bold</item>
<item name="android:background">@drawable/raised_button_background</item>
<item name="android:letterSpacing">0.025</item>
<item name="android:textAllCaps">false</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
</style>
In drawable create the background of the raised button background raised_button_background.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/highlight_light">
<item>
<shape android:shape="rectangle">
<solid android:color="?colorPrimary" />
<corners android:radius="4dp" />
</shape>
</item>
</ripple>
Create the alert dialog
Now the main coding part begins, just add following files to create your custom material dialog, create AlertDimDialog.kt which extends the app compact dialog
package com.app.tasker.custom
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.*
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatDialog
import androidx.core.content.res.ResourcesCompat
import com.app.tasker.R
class AlertDimDialog(context: Context?) : AppCompatDialog(context, R.style.Theme_Tasker_Dialog) {
init {
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.run {
// Spread the dialog as large as the screen.
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
}
override fun setContentView(view: View?) {
if (view != null) {
super.setContentView(wrap(view))
}
}
private fun wrap(content: View): View {
val res = context.resources
val verticalMargin = res.getDimensionPixelSize(R.dimen.dialog_vertical_margin)
val horizontalMargin = res.getDimensionPixelSize(R.dimen.dialog_horizontal_margin)
return FrameLayout(context).apply {
addView(content, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(horizontalMargin, verticalMargin, horizontalMargin, verticalMargin)
gravity = Gravity.CENTER
})
val rect = Rect()
setOnTouchListener { _, event ->
when (event.action) {
// The FrameLayout is technically inside the dialog, but we treat it as outside.
MotionEvent.ACTION_DOWN -> {
content.getGlobalVisibleRect(rect)
if (!rect.contains(event.x.toInt(), event.y.toInt())) {
cancel()
true
} else {
false
}
}
else -> {
false
}
}
}
background = ColorDrawable(ResourcesCompat.getColor(res, R.color.scrim, context.theme))
}
}
}
Now create a custom dialog fragment, the main advantage of dialog fragment over dialog is that it can survive the configuration changes.
open class AlertDimDialogFragment : AppCompatDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDimDialog(context)
}
}
Finally create the alert dialog class which you will use to show alert dialog
package com.app.tasker.custom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.app.tasker.R
import kotlinx.android.synthetic.main.custom_dialog.*
class CustomDialog : CustomDimDialogFragment() {
companion object {
const val TITLE = "title"
const val DESC = "desc"
const val POSITIVE_BUTTON = "positive_button"
const val NEGATIVE_BUTTON = "negative_button"
private var mOnCustomDialogClick: OnAlertDialogClick? = null
fun invoke(title: String, desc: String, positiveButton: String, negativeButton: String, alertDialogClick: OnAlertDialogClick): CustomDialog {
val customDialog = CustomDialog()
val bundle = Bundle().apply {
putString(TITLE, title)
putString(DESC, desc)
putString(POSITIVE_BUTTON, positiveButton)
putString(NEGATIVE_BUTTON, negativeButton)
}
mOnCustomDialogClick = alertDialogClick
customDialog.arguments = bundle
return customDialog
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val inflate = inflater.inflate(R.layout.custom_dialog, container, false)
return inflate
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
arguments?.apply {
tvTitle.text = getString(TITLE)
tvDesc.text = getString(DESC)
btNegative.text = getString(NEGATIVE_BUTTON)
btPositive.text = getString(POSITIVE_BUTTON)
}
btNegative.setOnClickListener {
dismiss()
mOnCustomDialogClick?.onClick(NegativeButton)
}
btPositive.setOnClickListener {
dismiss()
mOnCustomDialogClick?.onClick(PositiveButton)
}
}
interface OnAlertDialogClick {
fun onClick(alertDialogSealed: AlertDialogSealed)
}
}
Here is the xml for above class, you can add more functionality here based on your requirements
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dialog_bg"
android:elevation="@dimen/dialog_elevation"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginBottom="@dimen/spacing_normal"
android:paddingHorizontal="@dimen/margin_large"
android:text="Sign In"
android:textAppearance="@style/TextAppearance.Tasker.DialogTitle" />
<TextView
android:id="@+id/tvDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:layout_marginBottom="@dimen/margin_normal"
android:paddingHorizontal="@dimen/margin_large"
android:text="Sign in to send feedback and survey"
android:textAppearance="@style/TextAppearance.Tasker.DialogContent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btNegative"
style="@style/Widget.Tasker.BorderlessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginBottom="@dimen/margin_normal"
android:text="Not Now" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btPositive"
style="@style/Widget.Tasker.RaisedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_normal"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginBottom="@dimen/margin_normal"
android:text="Sign In" />
</LinearLayout>
</LinearLayout>
Congratulations your material alert dialog is ready to use, here is the look of your alert dialog.
One thing to remember here is that in case your button is just having one action button then do not add a background for that button.