The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Custom Dialogs and Preferences

Android ships with a number of dialog classes for specific circumstances, like DatePickerDialog and ProgressDialog. Similarly, Android comes with a smattering of Preference classes for your PreferenceActivity, to accept text or selections from lists and so on.

However, there is plenty of room for improvement in both areas. As such, you may find the need to create your own custom dialog or preference class. This chapter will show you how that is done.

We start off by looking at creating a custom AlertDialog, not by using AlertDialog.Builder, but via a custom subclass. Then, we show how to create your own dialog-style Preference, where tapping on the preference pops up a dialog to allow the user to customize the preference value.

Prerequisites

Understanding this chapter requires that you have read the chapter on dialogs, along with the chapter on the preference system. Also, the samples here use the custom ColorMixer View described in another chapter.

Your Dialog, Chocolate-Covered

For your own application, the simplest way to create a custom AlertDialog is to use AlertDialog.Builder, as described in a previous chapter. You do not need to create any special subclass — just call methods on the Builder, then show() the resulting dialog.

However, if you want to create a reusable AlertDialog, this may become problematic. For example, where would this code to create the custom AlertDialog reside?

So, in some cases, you may wish to extend AlertDialog and supply the dialog’s contents that way, which is how TimePickerDialog and others are implemented. Unfortunately, this technique is not well documented. This section will illustrate how to create such an AlertDialog subclass, as determined by looking at how the core Android team did it for their own dialogs.

The sample code is ColorMixerDialog, a dialog wrapping around the ColorMixer widget shown in a previous chapter. The implementation of ColorMixerDialog can be found in the CWAC-ColorMixer GitHub repository, as it is part of the CommonsWare Android Components.

Using this dialog works much like using DatePickerDialog or TimePickerDialog. You create an instance of ColorMixerDialog, supplying the initial color to show and a listener object to be notified of color changes. Then, call show() on the dialog. If the user makes a change and accepts the dialog, your listener will be informed.

The ColorMixerDialog
Figure 625: The ColorMixerDialog

Basic AlertDialog Setup

The ColorMixerDialog class is not especially long, since all of the actual color mixing is handled by the ColorMixer widget:

package com.commonsware.cwac.colormixer;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;

public class ColorMixerDialog extends AlertDialog
  implements DialogInterface.OnClickListener {
  static private final String COLOR="c";
  private ColorMixer mixer=null;
  private int initialColor;
  private ColorMixer.OnColorChangedListener onSet=null;
  
  public ColorMixerDialog(Context ctxt,
                          int initialColor,
                          ColorMixer.OnColorChangedListener onSet) {
    super(ctxt);
    
    this.initialColor=initialColor;
    this.onSet=onSet;
    
    mixer=new ColorMixer(ctxt);
    mixer.setColor(initialColor);
    
    setView(mixer);
    setButton(ctxt.getText(R.string.cwac_colormixer_set),
              this);
    setButton2(ctxt.getText(R.string.cwac_colormixer_cancel),
               (DialogInterface.OnClickListener)null);
  }
  
  @Override
  public void onClick(DialogInterface dialog, int which) {
    if (initialColor!=mixer.getColor()) {
      onSet.onColorChange(mixer.getColor());
    }
  }
  
  @Override
  public Bundle onSaveInstanceState() {
    Bundle state=super.onSaveInstanceState();
    
    state.putInt(COLOR, mixer.getColor());
    
    return(state);
  }

  @Override
  public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
      
    mixer.setColor(state.getInt(COLOR));
  }
}

We extend the AlertDialog class and implement a constructor of our own design. In this case, we take in three parameters:

  1. A Context (typically an Activity), needed for the superclass
  2. The initial color to use for the dialog, such as if the user is editing a color they chose before
  3. A ColorMixer.OnColorChangedListener object, just like ColorMixer uses, to notify the dialog creator when the color is changed

We then create a ColorMixer and call setView() to make that be the main content of the dialog. We also call setButton() and setButton2() to specify a “Set” and “Cancel” button for the dialog. The latter just dismisses the dialog, so we need no event handler. The former we route back to the ColorMixerDialog itself, which implements the DialogInterface.OnClickListener interface.

Handling Color Changes

When the user clicks the “Set” button, we want to notify the application about the color change…if the color actually changed. This is akin to DatePickerDialog and TimePickerDialog only notifying you of date or times if the user clicks Set and actually changed the values.

The ColorMixerDialog tracks the initial color via the initialColor data member. In the onClick() method — required by DialogInterface.OnClickListener — we see if the mixer has a different color than the initialColor, and if so, we call the supplied ColorMixer.OnColorChangedListener callback object:

  @Override
  public void onClick(DialogInterface dialog, int which) {
    if (initialColor!=mixer.getColor()) {
      onSet.onColorChange(mixer.getColor());
    }
  }

State Management

Dialogs use onSaveInstanceState() and onRestoreInstanceState(), just like activities do. That way, if the screen is rotated, or if the hosting activity is being evicted from RAM when it is not in the foreground, the dialog can save its state, then get it back later as needed.

The biggest difference with onSaveInstanceState() for a dialog is that the Bundle of state data is not passed into the method. Rather, you get the Bundle by chaining to the superclass, then adding your data to the Bundle it returned, before returning it yourself:

  @Override
  public Bundle onSaveInstanceState() {
    Bundle state=super.onSaveInstanceState();
    
    state.putInt(COLOR, mixer.getColor());
    
    return(state);
  }

The onRestoreInstanceState() pattern is much closer to the implementation you would find in an Activity, where the Bundle with the state data to restore is passed in as a parameter:

  @Override
  public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
      
    mixer.setColor(state.getInt(COLOR));
  }

Preferring Your Own Preferences, Preferably

The preview of this section was fed to a gremlin, after midnight.