There are a lot of things the Android framework does for you, particularly when it comes to the user interface. Between the standard Dialog class and the AlertDialog builder, 99% of the time the folks at Google have you covered if you need to pop up an alert or get a quick yes or no answer from the user. Sure, if you are coming from Windows and .NET, it takes a little getting used to the idea that there is no such beast as a native modal dialog, but overall not a lot of effort is required on the part of the developer to perform what has become a pretty standard user interaction paradigm.
The only caveat is that when you let the framework do the heavy lifting you end up with the system dialogs for the particular version of Android your app happens to be running on. So your dialog running on Gingerbread looks different than your dialog running on Ice Cream Sandwich, which looks different yet than your dialog running on Froyo. This is by in large how it should be, but there are times, especially when you have an app with a highly custom user experience (think Angry Birds), where you want fine control over even this seemingly small aspect of your app.
Fortunately, on the Android platform if you dig deep enough you can usually find a way to override just about any behavior, and dialogs are no exception. The code that follows demonstrates a custom dialog class I used in a game where the color of the user interface changed as a player progressed through the levels. Feel free to follow along with the tutorial below, or download the entire project here and import it into Eclipse directly.
1. Start a new project in Eclipse. Target Android 1.6 or higher. Be sure to change the name of the startup activity to Main.java.
2. Let’s begin in the /res/layout folder. We need to modify main.xml to include three buttons, which we will dock at the bottom of the UI via a relative layout.
main.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="Rainbow Dialog Demo" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_alignParentBottom="true" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/red"
android:text="red"
android:layout_gravity = "left"
android:padding="20dip"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/blue"
android:text="blue"
android:layout_gravity = "center"
android:padding="20dip"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/green"
android:text="green"
android:layout_gravity = "right"
android:padding="20dip"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
3. We will create the template for our custom dialog. Again working in the /res/layout folder, this time we will create a new xml file called rainbow.xml. At first glance, the nested layouts in rainbow.xml might appear strange, but this is how we get a highly custom border, header, and title on the dialog. If you’re still scratching your head, just go with it for now and things will clear up when we implement the RainbowDialog class.
rainbow.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:id="@+id/parent"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="3dip"
android:background="#000000">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="3dip"
android:id="@+id/header"
android:background="#ffffff">
<TextView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold"
android:text="Title"
android:padding="8dip"
android:singleLine="true"
android:id="@+id/title"
android:background="#ffffff"
android:layout_gravity="left|center"
android:gravity="left|center"/>
<TextView
android:padding="8dip"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="#000000"
android:textSize="22sp"
android:textStyle="bold"
android:text="X"
android:clickable="true"
android:singleLine="true"
android:id="@+id/close"
android:background="#ffffff"
android:layout_gravity="right|top"
android:gravity="right|top"/>
</LinearLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#ffffff"
android:text="Custom dialog text goes here."
android:layout_gravity="center"
android:gravity="center"
android:id="@+id/message"
android:background="#000000"
android:padding="20dip"/>
</LinearLayout>
</LinearLayout>
4. Moving on to the /src directory create a new class called RainbowDialog.java. The class should extend Dialog and implement OnTouchListener. We use the OnTouchListener to allow the user to close the dialog, but the real work happens in the custom constructor and the onCreate override.
RainbowDialog.java package com.authorwjf.rainbow; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.Window; import android.widget.TextView; public class RainbowDialog extends Dialog implements OnTouchListener { private int mColor = 0; private String mTitle = null; private String mMessage = null; public RainbowDialog(Context context, String title, String message, int color) { super(context);mTitle=title;
mMessage=message;
mColor=color;
}
public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { return true;}
return super.onKeyDown(keyCode, event);}
@Override
public boolean onTouch(View v, MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_DOWN) { if (v.getId()==R.id.close) {dismiss();
return true;}
}
return false;}
@Override
public void onCreate(Bundle savedInstanceState) {requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.rainbow);
View v = findViewById(R.id.header);
v.setBackgroundColor(mColor);
v = findViewById(R.id.parent);
v.setBackgroundColor(mColor);
TextView tv = (TextView)findViewById(R.id.close);
tv.setBackgroundColor(mColor);
tv.setOnTouchListener(this);tv = (TextView)findViewById(R.id.message);
tv.setTextColor(mColor);
tv.setText(mMessage);
tv = (TextView)findViewById(R.id.title);
tv.setText(mTitle);
tv.setBackgroundColor(mColor);
}
}
5. We need to implement our /src/Main.java file. We hook our buttons, and respond appropriately in the onClick handler by instantiating a new instance of our RainbowDialog class, passing in a custom title, message, and color.
Main.java package com.authorwjf.rainbow; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class Main extends Activity implements OnClickListener{/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.main);
Button b = (Button)findViewById(R.id.blue);
b.setOnClickListener(this);b = (Button)findViewById(R.id.red);
b.setOnClickListener(this);b = (Button)findViewById(R.id.green);
b.setOnClickListener(this);}
@Override
public void onClick(View v) { RainbowDialog d = null; switch (v.getId()) { case R.id.green: d = new RainbowDialog(this,"Green","You requested a green dialog!", Color.GREEN); break; case R.id.blue: d = new RainbowDialog(this,"Blue","You requested a blue dialog!", Color.BLUE); break; case R.id.red: d = new RainbowDialog(this,"Red","You requested a red dialog!", Color.RED); break;}
if (d!=null) {d.show();
}
}
}
There you have it — a highly customized implementation of a dialog box. Obviously, customizing every dialog across every app you write would make for a poor user experience; however, it’s nice to know that when you deem it necessary, the platform is flexible enough to bend to your will. It’s one of the many reasons why I like working with the Android platform.
Figure A
Figure B
Figure C