Android provides a AlertDialog class that ease the building of Dialog windows throught it’s Builder inner class. However, it’s not possible to customize these AlertDialog windows. To give AlertDialog windows a custom Look and Feel, a solution is to create application specific AlertDialog and AlertDialog.Builder class.
Defining the Look And Feel
What we want is to turn the default AlertDialog Look and Feel into this custom Look and Feel :
The dialog will support the following features :
- Specify title from resource or String
- Specify content from resource, String or custom layout
- Set positive and negative buttons and associated listeners
Writing layout, style and theme
The dialog will use a custom layout to render it’s content. The layout defines the ids that will be used to access the title TextView, the dialog message or custom content and the buttons bar.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:minWidth="280dip" android:layout_height="wrap_content"> <LinearLayout android:orientation="vertical" android:background="@drawable/header" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView style="@style/DialogText.Title" android:id="@+id/title" android:paddingRight="8dip" android:paddingLeft="8dip" android:background="@drawable/title" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> <LinearLayout android:id="@+id/content" android:orientation="vertical" android:background="@drawable/center" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView style="@style/DialogText" android:id="@+id/message" android:padding="5dip" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout> <LinearLayout android:orientation="horizontal" android:background="@drawable/footer" android:layout_width="fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/positiveButton" android:layout_marginTop="3dip" android:layout_width="0dip" android:layout_weight="1" android:layout_height="wrap_content" android:singleLine="true"/> <Button android:id="@+id/negativeButton" android:layout_marginTop="3dip" android:layout_width="0dip" android:layout_weight="1" android:layout_height="wrap_content" android:singleLine="true"/> </LinearLayout> </LinearLayout>
The root LinearLayout’s width is set to fill_parent with a minimum of 280dip so that the dialog width will always be 87,5% of the screen width.
A custom Theme should be used to declare the dialog as floating and using a custom background and a custom title view :
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Dialog" parent="android:style/Theme.Dialog"> <item name="android:windowBackground">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> </style> </resources>
Then we need to define the appearence of our dialog title and message :
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="DialogText"> <item name="android:textColor">#FF000000</item> <item name="android:textSize">12sp</item> </style> <style name="DialogText.Title"> <item name="android:textSize">16sp</item> <item name="android:textStyle">bold</item> </style> </resources>
Writing the Dialog and Builder class
It’s preferable for our custom Builder to have the same methods that we have in the AletDialog.Builder class.
package net.androgames.blog.sample.customdialog.dialog; import net.androgames.blog.sample.customdialog.R; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; /** * * Create custom Dialog windows for your application * Custom dialogs rely on custom layouts wich allow you to * create and use your own look & feel. * * Under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html * * @author antoine vianey * */ public class CustomDialog extends Dialog { public CustomDialog(Context context, int theme) { super(context, theme); } public CustomDialog(Context context) { super(context); } /** * Helper class for creating a custom dialog */ public static class Builder { private Context context; private String title; private String message; private String positiveButtonText; private String negativeButtonText; private View contentView; private DialogInterface.OnClickListener positiveButtonClickListener, negativeButtonClickListener; public Builder(Context context) { this.context = context; } /** * Set the Dialog message from String * @param title * @return */ public Builder setMessage(String message) { this.message = message; return this; } /** * Set the Dialog message from resource * @param title * @return */ public Builder setMessage(int message) { this.message = (String) context.getText(message); return this; } /** * Set the Dialog title from resource * @param title * @return */ public Builder setTitle(int title) { this.title = (String) context.getText(title); return this; } /** * Set the Dialog title from String * @param title * @return */ public Builder setTitle(String title) { this.title = title; return this; } /** * Set a custom content view for the Dialog. * If a message is set, the contentView is not * added to the Dialog... * @param v * @return */ public Builder setContentView(View v) { this.contentView = v; return this; } /** * Set the positive button resource and it's listener * @param positiveButtonText * @param listener * @return */ public Builder setPositiveButton(int positiveButtonText, DialogInterface.OnClickListener listener) { this.positiveButtonText = (String) context .getText(positiveButtonText); this.positiveButtonClickListener = listener; return this; } /** * Set the positive button text and it's listener * @param positiveButtonText * @param listener * @return */ public Builder setPositiveButton(String positiveButtonText, DialogInterface.OnClickListener listener) { this.positiveButtonText = positiveButtonText; this.positiveButtonClickListener = listener; return this; } /** * Set the negative button resource and it's listener * @param negativeButtonText * @param listener * @return */ public Builder setNegativeButton(int negativeButtonText, DialogInterface.OnClickListener listener) { this.negativeButtonText = (String) context .getText(negativeButtonText); this.negativeButtonClickListener = listener; return this; } /** * Set the negative button text and it's listener * @param negativeButtonText * @param listener * @return */ public Builder setNegativeButton(String negativeButtonText, DialogInterface.OnClickListener listener) { this.negativeButtonText = negativeButtonText; this.negativeButtonClickListener = listener; return this; } /** * Create the custom dialog */ public CustomDialog create() { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // instantiate the dialog with the custom Theme final CustomDialog dialog = new CustomDialog(context, R.style.Dialog); View layout = inflater.inflate(R.layout.dialog, null); dialog.addContentView(layout, new LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); // set the dialog title ((TextView) layout.findViewById(R.id.title)).setText(title); // set the confirm button if (positiveButtonText != null) { ((Button) layout.findViewById(R.id.positiveButton)) .setText(positiveButtonText); if (positiveButtonClickListener != null) { ((Button) layout.findViewById(R.id.positiveButton)) .setOnClickListener(new View.OnClickListener() { public void onClick(View v) { positiveButtonClickListener.onClick( dialog, DialogInterface.BUTTON_POSITIVE); } }); } } else { // if no confirm button just set the visibility to GONE layout.findViewById(R.id.positiveButton).setVisibility( View.GONE); } // set the cancel button if (negativeButtonText != null) { ((Button) layout.findViewById(R.id.negativeButton)) .setText(negativeButtonText); if (negativeButtonClickListener != null) { ((Button) layout.findViewById(R.id.negativeButton)) .setOnClickListener(new View.OnClickListener() { public void onClick(View v) { positiveButtonClickListener.onClick( dialog, DialogInterface.BUTTON_NEGATIVE); } }); } } else { // if no confirm button just set the visibility to GONE layout.findViewById(R.id.negativeButton).setVisibility( View.GONE); } // set the content message if (message != null) { ((TextView) layout.findViewById( R.id.message)).setText(message); } else if (contentView != null) { // if no message set // add the contentView to the dialog body ((LinearLayout) layout.findViewById(R.id.content)) .removeAllViews(); ((LinearLayout) layout.findViewById(R.id.content)) .addView(contentView, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } dialog.setContentView(layout); return dialog; } } }
Using the custom Builder
Using the custom Builder is just as simple as using the default AlertDialog.Builder :
/** * Build the desired Dialog * CUSTOM or DEFAULT */ @Override public Dialog onCreateDialog(int dialogId) { Dialog dialog = null; switch (dialogId) { case CUSTOM_DIALOG : CustomDialog.Builder customBuilder = new CustomDialog.Builder(CustomDialogActivity.this); customBuilder.setTitle("Custom title") .setMessage("Custom body") .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { CustomDialogActivity.this .dismissDialog(CUSTOM_DIALOG); } }) .setPositiveButton("Confirm", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); dialog = customBuilder.create(); break; case DEFAULT_DIALOG : AlertDialog.Builder alertBuilder = new AlertDialog.Builder(CustomDialogActivity.this); alertBuilder.setTitle("Default title") .setMessage("Default body") .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton("Confirm", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { CustomDialogActivity.this .dismissDialog(DEFAULT_DIALOG); } }); dialog = alertBuilder.create(); break; } return dialog; }
You can browse the full source code of the Eclipse project here : SampleCustomDialog
Enjoy !