Android: Introduction to a simple calculator

10 Mar 2012

So you want to make your first program in Android. One of the easiest applications to learn building GUI's in any programming language is a calculator. So that's what I will show you in this post. It's a step by step guide on how to build a good looking calculator using Android. I put emphasis on the GUI part and not the calculator logic. I included dialogs, buttons with custom background, custom images,... .

Introduction to Android application development

Android provides an SDK for almost any platform to program on. I assume you have managed to install your SDK on ubuntu, windows or OS:X. I used ubuntu for this tutorial. In android you have two things to work with:

  • Java code
  • XML
If you are used to building applications with swing you might always define parameters within your code. In android you define the properties of an object, such as size,placement,content or type, in the XML file. It gives you a better way to manage layouts. You needn't change your code if you want a special layout in landscape or normal mode. You just redefine parameters in the XML depending on screen-size or orientation. You can also define variables in your XML, which makes it easy to reuse them in different methods or classes.

Creating the project

First of all create a new Android project in Eclipse. You can choose what SDK to use, I picked 2.3.3, but this will mean we need to rebuild the application if we want support for lower Android versions.

Defining a layout

A problem with Android is that you need to support different types and resolutions of screens. The layout of buttons and text-fields is defined in an XML file. When you look at the folder structure you can see a folder called "res", if you open it you see these folders:
  • drawable
  • drawable-hdpi
  • drawable-ldpi
  • drawable-mdpi
  • layout
  • values
Hdpi,ldpi and mdpi are screen resolutions. Check this image provided by google: It's important to know what the different resolutions are when you create a game or application. Different screen types might mean different image sizes. The two possibilities are layout and layout-land. The first is when you hold your tablet up, the landscape view is when you hold it on its side. The resolutions I want to support are WVGA devices (phones like the Desire HD) and XWGA devices (tablets like the HP Touchpad). So to be able to support both of them in portrait and landscape mode, I will add three extra folders (layout-land isn't created by default):
  • layout-land
  • layout-hdpi
  • layout-land-hdpi
The WVGA will use the main.xml's defined in layout-hdpi and layout-land-hdpi, whereas the tablet will use the normal layout and layout-land. These folder names are reserved for this purpose. Let's start with the normal view. Open main.xml, you normally get the visual editor. if you click main.xml on the bottom you will open the xml file in the editor. This is my layout/main.xml. This will be used for the WXGA resolution. At the top corner of the visual editor you can change the type of screen. I prefer to define everything by hand with the XML text editor and just check the layout in the visual editor, rather than using the visual editor itself. When creating a layout you need to define the orientation. A vertical orientation gives stacked items and horizontal places items next to each other. Another image from Google to show the difference:
I wanted a layout like this: To achieve this I first defined a LinearLayout with a vertical orientation:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

fill_parent means to fill the container it's in, in this case the whole screen. Now we want to stack rows with buttons and text-fields. Let's start at the top, which are two text-fields. I defined another LinearLayout within our previous LinearLayout that contains the two text-fields:

<LinearLayout
        android:id="@+id/linearLayoutH1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight=".25"
        android:orientation="vertical"
        android:weightSum="1" >
        <EditText
            android:id="@+id/result"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:editable="false"
            android:layout_weight=".50"
             />
        <EditText
            android:id="@+id/entry"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:editable="false"
            android:layout_weight=".50"
            />
    </LinearLayout>

Layout:

  • android:id="@+id/linearLayoutH1" This is the id for this Linear layout/item
  • android:layoutheight="wrapcontent" Make it as high as needed for its content
  • android:weightSum="1" The weight of the container, this is used by items in the container to define their size
  • android:layoutwidth="fillparent" use the whole width of the container
  • android:layout_weight=".50" I wanted both text-boxes to take up equal amount of vertical space
  • android:editable="false" The user shouldn't be typing directly in the text-box
Next we will define 3 buttons in another LinearLayout contained in our first container. One button has an image, the two others just contain text:

 <LinearLayout
        android:id="@+id/linearLayoutH3"
        android:layout_width="fill_parent"
        android:layout_height="100dp"
        android:orientation="horizontal"
        android:weightSum="1.0" >
        <Button
            android:id="@+id/buttonClear"
            style="@style/ButtonText"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:background="@drawable/custombuttonred"
            android:text="@string/clear"
            android:layout_weight=".43"
            android:textSize="40sp" />
          <ImageButton
            style="@style/ButtonText"
            android:id="@+id/c101_image"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:scaleType="centerInside"
            android:background="@drawable/cloud101"
            android:layout_weight=".14" />
         <Button
            android:id="@+id/buttonBackspace"
            style="@style/ButtonText"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:background="@drawable/custombuttonred"
            android:text="@string/backspace"
            android:layout_weight=".43"
            android:textSize="30sp" />
    </LinearLayout>
  • ```xml android:orientation="horizontal" ``` Our LinearLayout's orientation
  • ```xml android:layout_height="100dp" ``` The height of the item, dp refers to density pixels, check below
  • ```xml android:background="@drawable/custombuttonred" ``` Within the drawable folder I have defined some more XML's for theming the buttons, more on this later
  • ```xml android:text="@string/clear" ``` This means look into the string.xml file contained in the values folder and look for the id "clear", more on this later
  • ```xml android:textSize="40sp" ``` For textsize we always use sp, check below
  • ```xml android:scaleType="centerInside" ``` This is how we will scale our image-button, it will scale the image uniformly.
  • ```xml android:background="@drawable/cloud101 ``` The image to use as background, I made an image called cloud101.png and put it in the drawable folder
  • ```xml style="@style/ButtonText" ``` Style for the button, check below
  • ```xml android:background="@drawable/custombuttonred" ``` Instead of using an image I used a theme defined in drawable/custombuttonred

We then define the other buttons in a similar way:

 <LinearLayout
        android:id="@+id/linearLayoutH2"
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:layout_weight=".25"
        android:orientation="horizontal"
        android:weightSum="1.0" >
        <Button
            android:id="@+id/button1"
            style="@style/ButtonText"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_weight=".25"
            android:background="@drawable/custombutton"
            android:text="@string/b1"
            android:textSize="40sp" />
        <Button
            android:id="@+id/button2"
            style="@style/ButtonText"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_weight=".25"
            android:background="@drawable/custombutton"
            android:text="2"
            android:textSize="40sp" />
        <Button
            android:id="@+id/button3"
            style="@style/ButtonText"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_weight=".25"
            android:background="@drawable/custombutton"
            android:text="3"
            android:textSize="40sp" />
        <Button
            android:id="@+id/buttonDevide"
            style="@style/ButtonText"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_weight=".25"
            android:background="@drawable/custombuttonblue"
            android:text="/"
            android:textSize="40sp" />
    </LinearLayout>

We repeat this for every row, it's pretty straight forward. Instead of always referring to a string in string.xml, you can also just hard code the string.

android:text="3"
Other resolutions

The problem we have now is that we need to support all the other resolutions as well. We just need to copy the main.xml to all the other folders we previously made (layout-hdpi,layout-land,...). And just tweak the layouts for those resolutions/orientations:

You should understand how they work. Have a look at them and test them with the graphical editor, using the right resolution and orientation setting.
Density independent pixels
An abstract unit that is based on the physical density of the screen. These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with the screen density, but not necessarily in direct proportion. Note: The compiler accepts both "dip" and "dp", though "dp" is more consistent with "sp".
Scale-independent Pixels
This is like the dp unit, but it is also scaled by the user's font size preference. It is recommend you use this unit when specifying font sizes, so they will be adjusted for both the screen density and user's preference.

Values

In android you can refer to values. This can be useful when you have large strings, you can refer to them in the file, rather then redefine them every time again. This is what my values/string.xml looks like. Name defines the id of the item and the content is between the tags.

Styling

When styling the buttons I used something found on the internet. I put it in values/styles.xml. I also wanted some fancy background colors, but I'm not really good at graphical stuff, so I looked around and found at dibbus by folkert (link removed due to malware risk). I just made these xml's:

Coding

After all of this XML you'd almost forget there is still a bit of code as well.

Calculator

Create a new package. I like to make different packages to sort things a bit, even for something small. I called my package com.cloud101.calculator and the class Calculator.java.

package com.cloud101.calculator;
public class Calculator {
    public double sum(double a, double b) {
        return a + b;
    }
    public String devide(double a, double b) {
        if (b == 0) {
            return "Can't devide by zero";
        } else {
            Double c = a / b;
            return c.toString();
        }
    }
    public double difference(double a, double b) {
        return a - b;
    }
    public double product(double a, double b) {
        return a * b;
    }
}

All of the methods return a double after a logic operation (so sum, difference, multiplying) except for the divide method that returns a string, it returns a string when you try to do an illegal operation (dividing by zero). We will use it later to show an error message in an error dialog.

Cloud101CalculatorActivity

So this is my activity class in Android, it's what is called when you run an application. I've put the full class here on pastebin. The first method which is called and needs to be overridden is onCreate():

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        textView = (EditText) findViewById(R.id.entry);
        resultView = (EditText) findViewById(R.id.result);
        ImageView image = (ImageView) findViewById(R.id.c101_image);
        ...
  • ```java super.onCreate(savedInstanceState); ``` Calls the super method of the Activity class, it's used for screen rotation
  • ```java setContentView(R.layout.main); ``` what XML to use for the layout
  • ```java textView = (EditText) findViewById(R.id.entry); resultView = (EditText) findViewById(R.id.result); ImageView image = (ImageView) findViewById(R.id.c101_image); ``` some declaration of local variables.

When we use

R.id.entry

. We refer to the id's we made in the xml files like:

android:id="@+id/button3"

Creating the button's:

final Button button0 = (Button) findViewById(R.id.button0);
        button0.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                numberDisplayed += "0";
                updateTextField();
            }
        });

We create a button, and we refer to a button we defined in the layout with

findViewById(R.id.button0)

We then add an actionlistener to the button. When the buttons is clicked or pushed it will do a few things. In this case it will add 0 to the numberDisplayed String and it will run the

updateTextField();

method. To update the screen with a certain value we use:

    private void updateTextField() {
        textView.setText(this.numberDisplayed);
        resultView.setText(this.resultNumber.toString());
    }

I will not go into the logic of the calculator operations as it is pretty straightforward.

Alert Window

There are some illegal mathematical actions you can't do with a calculator, like dividing by zero. When a user tries to divide by zero he will get an Alert popup on his screen. I created a new package called com.cloud101.windows and made this alert window class.

public class Alertwindow {
    public Alertwindow(Activity activity,String message){
    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder.setMessage(message)
           .setCancelable(false)
           .setPositiveButton("Ok",  new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    dialog.dismiss();
               }
           });
    AlertDialog alert = builder.create();
    alert.show();
    }
}

We need to pass an activity to the class and a message. The message will be displayed in the alertwindow. To dismiss the alertwindow we make a button that says "Ok". When "Ok" is clicked we dismiss the dialog. To display the dialog when an Alertwindow object is created we call

alert.show()

. In the processNumbers method of Cloud101CalculatorActivity, we create a new alert Alertwindow when someone tries to devide by zero:

} else if (devide) {
            String resultDev = calc.devide(resultNumber, Double.parseDouble(numberDisplayed));
            if(resultDev.equals("Can't devide by zero")){
                devideAlert();
            }else{
                resultNumber = new Double(resultDev);
            }

In devideAlert() we find:

    protected void devideAlert() {
        Alertwindow alert = new Alertwindow(this,"Can't devide by zero");
    }

Opening URL's from your application

When someone clicks the cloud101 logo, the application gets suspended a web-browser is opened that displays the cloud101.eu website:

        final ImageButton c101_image = (ImageButton) findViewById(R.id.c101_image);
        c101_image.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://cloud101.eu/blog"));
                startActivity(browserIntent);
            }
        });

When the button is clicked we make a new browser intent, after which the browser is opened and redirected to this blog.

Building the application

Now we have finished our project we can test the code in the Android emulator or a fysical device. After it runs okay, you can build the application by right-clicking the application and exporting it as an Android app. If you want to support lower versions of Android than 2.3.3, you need to open the AndroidManifest.xml and look for this line:

  <uses-sdk android:minSdkVersion="10" />

If you haven't used any methods that are only available from 2.3.3 and up, you can lower this number and have support for legacy devices. I used 8 instead of 10.

Interesting reading

Some more interesting reading would be the developer guide and the resources.

Final Word

I hope this tutorial was a bit comprehensive for you. If you have questions or suggestions you can always place them in the comments. If you want to download the application, you can find it here with all the sources attached.

Edit 1

Updated the link, mediafire deleted my zip. Mirrored now on hotfile.

md5: 01a6e1cfaa1310dc917ae8921045600d
sha1: 91af3a3e43e9f43112e513b72ba36ded5835f4e9