January 24th, 2009 - Connor Garvey
Update
The Android developer site has some great icon guidelines now. I recommend taking a look. If you have Photoshop or another program that supports vector shapes and drop-shadows, you can see the proper settings there.
Nick says
… you can do the whole thing in Inkscape without having to go into GIMP, by using a clip mask.
Duplicate your arrow, then select both this and the inverse arrow and do Object > Clip > Set. The inverse arrow will be clipped to only show the content contained within the arrow shape.
Introduction
For my Android application, Green Mileage, I created a number input dialog box. One of the buttons in the dialog is a backspace key. Right now, it’s just a button that says, “Back”. When typing, it’s function is not immediately intuitive, so I wanted to replace it with an ImageButton. To skip to the example, scroll down. Unfortunately, none of the Android icons look quite right. I found an image of a red X and a trash can. Neither would look like a backspace, so I decided to create my own.
Since Android is open source, I assumed that Google would have created some kind of template for button images. I thought I would find a PSD, but after much searching, could not. Rather than pay $1,000 for Photoshop, I decided to use open source applications to create the image. Here’s how I did it.
Step 1: Get Inkscape and GIMP
Since this drawing will be made with vector graphics, it’s best to use a vector graphics program, so head on over to Inkscape’s web site and download it if you don’t already have it. The post-processing will be done in GIMP, so grab a copy of that too. Both have versions for Linux and Windows.
Step 2: Create the Inkscape drawing
The points on the arrow should be evenly spaced, so they shouldn’t be drawn by hand. The arrow is really just a triangle with a rectangle dangling from its side.
- This tutorial is about drawing an arrow shape, but you can use most of these steps for any icon.
- First, let’s draw the arrow. Pick the polygon tool:
- In the toolbar at the top of the screen, set these values.
- Corners: 3
- Spoke ratio: 0.5
- Rounded: 0
- Randomized: 0
- In the drawing canvas click and hold. While holding the left mouse button, press and hold the control key. That will help you draw the triangle so that the right side is vertical.
- Select the rectangle tool and draw a rectangle for the right side of the arrow. Make it overlap with the triangle. Then, select both objects. In the menu bar, click Object -> Align and Distribute. Click the “Center on horizontal axis” button.
- Now, with both objects selected, in the menu bar, click Path -> Union.
- Android menu buttons have rounded corners and gradient fills. Let’s apply those next. With the arrow selected, in the menu bar, click Object -> Fill and Stroke. Click the Stroke Style tab. Select the round join and set the width to a high enough value that the corners look rounded.
- Select the Stroke Paint tab and pick the linear gradient. The gradient will appear sideways, but we’ll fix it later. Click Duplicate to create an Android button gradient. Click Edit… Select the first stop at the top of the window, then at the bottom of the window, set its RGBA value to 666b66ff. Set the second stop to b2afb2ff.
- Select the Fill tab, create a linear gradient and set it to the one you just created for the stroke.
- Close the Fill and Stroke dialog.
- To rotate the gradient, click the gradient tool, .
- You’ll see two handles. Drag the lighter side to the top of the arrow and the darker side to the bottom. The handles will snap in to place.
- Android menu buttons also have an inner shadow. Make a copy of the arrow away from the original arrow and draw a rectangle around it. Make it a good amount larger. Align the rectangle and the arrow the same way the triangle and rectangle were aligned before. Then, select both the rectangle and the arrow and in the menu bar, select Path -> Exclusion. Set the fill of the new shape to black and the path to the same gradient as the arrow, ensuring that the gradient minimum and maximum are aligned to the arrow, not the box. Make a duplicate of the new shape and set it aside for use later.
- Select both the original and the new, inverse arrow and align them horizontally and vertically.
- Select the larger, inverse arrow and open its Fill and Stroke options, move the blur slider to the right until it looks like a shadow. Also adjust the alpha of the fill option until the arrow looks like it has an inner shadow. Don’t worry if the space outside of the arrow doesn’t look right. Then, since Android’s shadows are offset, move the inverse arrow down and to the right a bit. The arrow keys are good for this.
- Now, in the copy of the inverse arrow you made, set the stroke to white and the fill color to black.
Go to part 2
January 18th, 2009 - Connor Garvey
Many of Android’s screens use attractive dividers with gradients that fade from black to white to black. Here’s how to create one of your own.
1. Create a file called “black_white_gradient.xml” in /res/drawable. Paste this into it.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#00000000"
android:centerColor="#FFFFFFFF"
android:endColor="#00000000"
android:angle="0" />
</shape>
2. Create a view in the target layout and apply the gradient as the background. The “black_white_gradient” in “@drawable/black_white_gradient” refers to the file name of the shape above.
<View android:id="@+id/divider"
android:background="@drawable/black_white_gradient"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_below="@id/someOtherThing" />
January 18th, 2009 - Connor Garvey
Recently, I created a screen with soft keys that could be used for number input. Scroll down for some source. Everything was working perfectly. Each number key called field.append(“1”), passing in the correct number. Then, a text watcher would be notified of the change and reformat the field. The problem arose when I wrote the backspace method. TextView doesn’t have a convenience method for removing characters from a field, so I used setText, passing in the number with the last character removed. Unfortunately, when I did that, the text wacher wasn’t notified of the change! Ugh… Android is very well designed, but some implementation details are really not written well. I decided it was time to look in the Android source. If the append method caused a notification to the text watcher, I would write a method that worked the same way.
Supporting number keys
This was really simple.
private class CharacterButtonListener implements
View.OnClickListener {
private TextView field;
private String character;
public CharacterButtonListener(TextView field,
String character) {
this.field = field;
this.character = character;
}
@Override
public void onClick(View v) {
this.field.append(this.character);
}
}
Supporting the backspace key
Since the setText method wasn’t notifying the TextWatcher, I looked into Android’s source to find this solution. It’s ugly, but it works.
this.buttonBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextView number = numberText;
CharSequence text = number.getText();
// This is annoyingly ugly, but is from the Android source
if (!(text instanceof Editable)) {
number.setText(text, BufferType.EDITABLE);
}
Editable editable = (Editable)number.getText();
// Now that we have the editable, edit it.
// This line is not from the Android source.
if (text.length() > 0) {
editable.delete(text.length() - 1, text.length());
}
}
});
That’s it! I tried another solution, having each soft-key fire a key event in the field. It worked, but since the field is automatically formatted, it didn’t feel right. Also, Android doesn’t let the application set the position of the cursor (caret) in the text view, so if the user re-opened the number input dialog to edit the number, the cursor appeared at the left side of the field, which meant that the user had to re-position the cursor before pressing any keys, which was a pain.
January 18th, 2009 - Connor Garvey
To support i18n, internationalization, Android provides a resource file, usually /res/values/strings.xml. That XML and others are compiled and each resource is assigned a unique integer ID. The IDs are placed into a resource file, “R”, as public static final variables. Here’s how to get the value of a string resource in your application.
In a layout XML
android:text="@string/resource_name"
In an Activity
this.getString(R.string.resource_name)
In an area of the program in which you have access to a Context or Application, such as a ListAdapter.
context.getString(R.string.resource_name)
application.getString(R.string.resource_name)
January 16th, 2009 - Connor Garvey
July 18, 2009: Updated for Android 1.5
Recently, I created a simple dial pad style number dialog. I wanted to make the numbers as large as possible to make finger input simple. Getting the buttons to stretch evenly and not look strange turned out to be a challenge, but I eventually arrived at a solution. For the full source, scroll down.
Big buttons in Android
- The dialog uses a relative layout. It’s usually best at placing objects on the screen no matter what the resolution or orientation.
- The numbers are contained in a table layout, which is good at spacing things evenly.
- To get the numbers to stretch to the bottom of the screen, layout_above is set to the OK button, which is aligned to the bottom of the dialog. The width is fill_parent so that the buttons fill the width of the screen.
- To get each row to be the same height in HTML, you would set the height to a percent. In Android, use layout_weight. Setting the weight of each row to the same value will make them the same size.
- Also use layout_weight on each button to even out their widths.
- In version 1.5, Android started to be “smart” about how it lays out items. Since the content of the “Back” button is longer than any other button, Android will make that column in the table wider. It will do so even though the layout weights of all of the items are the same. To override that behavior, set the width of every item to 0. That way, when Android stretches the buttons out to fit in the table, every button will start from the same width, 0.
<?xml version="1.0" encoding="utf-8"?>
<!--
Layout for a number input dialog
Author: Connor Garvey
Created: Jan 11, 2009
Version 0.0.4
Since 0.0.4
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="7dip">
<EditText android:id="@+id/number"
android:background="@android:drawable/editbox_background"
android:cursorVisible="false"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<View android:id="@+id/numberSeparator0"
android:layout_width="fill_parent"
android:layout_height="4dp"
android:layout_below="@id/number" />
<View android:id="@+id/numberSeparator1"
android:background="@drawable/black_white_gradient"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_below="@id/numberSeparator0" />
<View android:id="@+id/numberSeparator2"
android:layout_width="fill_parent"
android:layout_height="4dp"
android:layout_below="@id/numberSeparator1" />
<Button android:id="@+id/ok"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/message_ok"
android:layout_alignParentBottom="true" />
<TableLayout android:id="@+id/row1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/numberSeparator2"
android:layout_above="@id/ok"
android:layout_weight="1">
<TableRow
android:layout_weight="1">
<Button android:id="@+id/n1"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="1"
android:layout_weight="1" />
<Button android:id="@+id/n2"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="2"
android:layout_weight="1" />
<Button android:id="@+id/n3"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="3"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_weight="1">
<Button android:id="@+id/n4"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="4"
android:layout_weight="1" />
<Button android:id="@+id/n5"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="5"
android:layout_weight="1" />
<Button android:id="@+id/n6"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="6"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_weight="1">
<Button android:id="@+id/n7"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="7"
android:layout_weight="1" />
<Button android:id="@+id/n8"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="8"
android:layout_weight="1" />
<Button android:id="@+id/n9"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="9"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_weight="1">
<TextView
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button android:id="@+id/n0"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="0"
android:layout_weight="1" />
<Button android:id="@+id/back"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:text="Back"
android:layout_weight="1" />
</TableRow>
</TableLayout>
</RelativeLayout>
It is difficult to get references to buttons in dialogs. You first have to inflate the dialog’s view, then you can find a button by its ID.
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.number_input_dialog, null);
this.button0 = (Button)view.findViewById(R.id.n0);
this.button0.setOnClickListener(new NumberButtonListener(this.number, "0"));