SlideShare ist ein Scribd-Unternehmen logo
1 von 54
Downloaden Sie, um offline zu lesen
Cool Clock
Helping children to read analog clocks

An introduction to developing custom 2D graphics Android applications
Richard Creamer
2to32minus1 @gmail.com

Links
Home Page
LinkedIn Profile
Android Market Cool Clock Product Listing
External Cool Apps Software Website
External Cool Clock Product Details Page

1
Copyright (c) Richard Creamer 2011 - All Rights Reserved
What are 2D Custom Graphics Components?

Below are several examples of 2D components previously developed by the author in Java/Swing:

2
Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved
What are 2D Custom Graphics Components?

Below are several examples of 2D components previously developed by author in Java/Swing:

Custom
keypad

“Gel”
button

Process
Editor

Joystick for
games,
robotics

Least-squares
data/curve
fitting

Spring force graph
layout of UML-like
OO diagram showing
classes &
interrelationships

Tiger Line map of
states, counties
and air fields

Binary Search Tree
compact graph
layout &
visualization 3
Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved
The Cool Clock Application
Learn Mode

Clock Mode

Modern Clock

Hands can be interactively
dragged. Includes dynamic
“Before” and “After” circular
arrows.

Traditional running clock - no
hand dragging, but supports
optional dynamic circular
arrows.

A simple “modern” running
clock. No numerals, minute
ticks, etc.
4

Copyright (c) Richard Creamer 2011 - All Rights Reserved
Android Intro...

5
Android - Getting Started
• Getting started with Android development and the Eclipse plug-in:
• Install the Java SE JDK and Java documentation:
• www.oracle.com/technetwork/java/javase/downloads/index.html
• Install Eclipse, the most efficient IDE for Android development:
• www.eclipse.org
• Install the Android SDK and the Android Eclipse Plug-In:
• www.developer.android.com/sdk
• After Java SE, Eclipse and the Android SDK are installed:
• Set up your PATH environment variable to include:
• AndroidInstallDirplatform-tools, AndroidInstallDirtools
• JavaSeInstallDirbin
• Use Eclipse to create a “Hello, Android!” application.
• Android development book suggestions:
• Hello, Android by Burnette ( 1934356565 )
• Android Wireless Development by Darcy and Conder ( 0321743016 )
• Professional Android 2 Application Development by Meier ( 0470565520 )

6
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Android Introduction
• Android is a Linux-based platform/OS for mobile devices from Google.
• Android is not the same as the Chrome OS.
• Android programming: http://developer.android.com/
• High-level programming is done in Java SE.
• Low-level programming ( NDK ) is done in C/C++.
• Android NDK URL: http://developer.android.com/sdk/ndk/
• Android enforces aggressive resource management
• Resources such as strings, menu and view layouts can be defined in XML or in code.
• Google recommends defining resources in XML vs. in code.
• Android programs have direct, programmatic access to:
• GPS and other sensors ( compass, accelerometer, orientation )
• Multi-touch input, internal & external storage
• Web, e-mail, SMS, Google Maps
• Camera & multi-media playing/recording
• Open GL ES 1.1 and 2.0
• Android apps must declare needed privileges and minimum Android version.
• Android includes a rich set of UI widgets/components and a basic SQLite database.
• Android app lifecycle diagram:
• http://developer.android.com/reference/android/app/Activity.html#Lifecycle
7
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Android Tools and App Categories
• Useful tools:
• adb.exe - useful command lines:
• C:>adb logcat ( prints event log )
• C:>adb logcat -c ( clears the logcat history )
• C:>adb install Clock.apk ( installs an app on a device or emulator )
• Dalvic Debug Monitor ( located in sdkDirtools )
• Run ddms.bat to launch this useful console application
• Explore Android file system, take app screenshots and much more
• Android Virtual Device Manager ( AVD Manager )
• Create multiple virtual devices w/ various Android OS/HW configurations.
• Enables testing on a range of HW configurations which may be unavailable
• WindowAndroid SDK and AVD Manager
• Sampling of Android app types:
• Activity - A user interface screen
• Intent - An action ( e.g., launch Help screen or send e-mail )
• Service - A long-running background task
• Content Provider - Data accessed via a custom API ( e.g. Contacts )
• Widget - Mini home-screen applications ( e.g. Weather )
• Wallpaper - Static or animated backgrounds
8
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Java SE Intro...

9
Java Introduction
• The Cool Clock project was coded entirely in Java SE and XML.
• Java attributes:
• Fast, mature, portable, object-oriented language with bright future
• Extensive I/O, Concurrency, Collections, Reflection and Generics support
• Source code is compiled into platform-independent byte code files
• Note: Android byte codes are not compatible with standard Java byte codes
• A platform-specific program called a Java Virtual Machine ( JVM ) is used to execute Java
applications. Some JVMs perform JIT compilation.
• On the Android platform, a special JVM, the Dalvic Virtual Machine, is used.
• Large websites such as Amazon rely on/use Java extensively
• Unlike C/C++, only the JVM needs to be ported and recompiled for different platforms
• A basic Java program: ( note that Java now has a printf() method )
MyProgram.java
public class MyProgram {
public static void main( String [] args ) {
System.out.printf( "%sn", "Hello, Introductory 2D Android PPT!" );
}
}

• Compiling a program: ( javac is the compiler )
C:> javac MyProgram.java  MyProgram.class
• If a class has a main() method, it can be run from the command line: ( java.exe is the JVM )
C:> java MyProgram
 Hello, Introductory 2D Android PPT!

10
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Java Introduction
• IDEs
• Popular IDEs for Java include Eclipse and NetBeans. Both are free.
• Eclipse is usually used for Android development because it has a highlyintegrated plug-in for Android.
• Books
• Many good introductory Java books exist, here are a few:
• Learning Java by Niemeyer and Knudsen ( 0596008732 )
• Core Java, Vols. I and II by Horstmann and Cornell ( 0132354764 )
• More advanced books:
• Effective Java by Bloch, 2nd Edition ( 0321356683 ) ( essential )
• Java Concurrency in Practice by Goetz ( 0321349601 )
• Concurrent Programming in Java by Lea ( 0201310090 )
• Java Threads by Oakes and Wong ( 0596007829 )
• Java Generics and Collections by Naftalin & Wadler ( 0596527756 )
• Java NIO by Hitchens ( 0596002882 )
• Java Network Programming by Harold, 3rd Edition ( 0596007218 )

11
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Android App Components

12
Components of an Android App
Android App

Manifest XML File
• Declares minimum Android OS version
• Declares app version ID
• Declares configuration changes handled by app
• Declares all Activities in app

Auto-Generated Java Files
• A Java class named “R”
• R.java contains static final int IDs for all resources

Main Activity Class
• Handles initialization tasks in onCreate()
• Sets app View component ( app content view )
• Handles menu events
• Launches other Activities
• Handles configuration change events ( orientation )
• Responds to Android app lifecycle state changes

Resources
• drawable ( bitmaps, icons )
• layout ( GUI layout/content )
• menu ( menu content )
• values ( strings, arrays )
• xml ( Preferences screen layout & content, etc. )

Other Activity Classes

Main View Class

• Sub-activities such as:
• PrefsActivity.java ( Preferences screen )
• AboutActivity.java ( About screen )
• HelpActivity.java ( Help screen )

• Extends android.view.View class
• Handles presentation ( drawing, widgets, forms )
• Handles touch events
• Example: ClockPanel.java

Other Utility Classes
• Other classes such as:
• Vector2d.java
Copyright (c) Richard Creamer 2011 - All Rights Reserved

13
Android App Lifecycle

14
Android Application Lifecycle
• Android can pause or destroy applications at any time
• Android sends various lifecycle state-change notifications to applications including:
• onCreate()
• onRestoreInstanceState() is called right after onCreate()
• onStart()
• onPause()
• onSaveInstanceState() is called right before onPause()
• onResume()
• onStop()
• onDestroy()
• Applications should override and respond appropriately to most of these notifications to preserve state
and to minimize resource and CPU use when not actively running
Free resources
and persist
state

Fetch prior state,
instantiate app
elements, call
setContentView()

App Launch

Pause worker threads
& persist state
onStop()

onPause()

Another
Activity to
foreground,
e.g.

onDestroy()

Process
killed

onCreate()

Activity
invisible
Resume worker
threads
onStart()

Event

Event

onResume()

Activity is
visible
15

Copyright (c) Richard Creamer 2011 - All Rights Reserved
Actual logcat Trace of Cool Clock State Notifications
Stopping worker threads is one example of how apps should respond to lifecycle notifications

Start Cool
Clock

I/ActivityManager( 69): Start proc net.maxuint32.coolapps for activity net.maxuint32.coolapps/.CoolClock: pid=279 uid=10041 gids={}
I/System.out( 279): >>>>>>>>>>>> CoolClock.onCreate() <<<<<<<<<<<
I/System.out( 279): >>>>>>>>>>>> ClockPanel Constructor <<<<<<<<<<< ( called by CoolClock )
I/System.out( 279): >>>>>>>>>>>> CoolClock.onStart() <<<<<<<<<<<
I/System.out( 279): >>>>>>>>>>>> CoolClock.onResume() <<<<<<<<<<<
I/System.out( 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<<
( called by CoolClock )
I/System.out( 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<<
( called by CoolClock )
I/ActivityManager( 69): Displayed activity net.maxuint32.coolapps/.CoolClock: 4724 ms (total 4724 ms)
I/System.out( 279): ClockRunThread threadState: Running
Initially, clock run thread is active
I/System.out( 279): ClockRunThread threadState: Running

Press Phone
Menu Button

I/System.out( 279): >>>>>>>>>>>> CoolClock.onCreateOptionsMenu() <<<<<<<<<<<
I/System.out( 279): ClockRunThread threadState: Running
I/System.out( 279): ClockRunThread threadState: Running

Press Settings
Button

I/ActivityManager( 69): Starting activity: Intent { cmp=net.maxuint32.coolapps/.PrefsActivity }
I/System.out( 279): >>>>>>>>>>>> CoolClock.onOptionsMenuClosed() <<<<<<<<<<<
I/System.out( 279): >>>>>>>>>>>> CoolClock.onPause() <<<<<<<<<<<
I/System.out( 279): >>>>>>>>>>>> ClockPanel.pause() <<<<<<<<<<< ( called by CoolClock )
I/ActivityManager( 69): Displayed activity net.maxuint32.coolapps/.PrefsActivity: 1970 ms (total)
I/System.out( 279): ClockRunThread threadState: Suspended
I/System.out( 279): ClockRunThread threadState: Suspended

Exit
Preferences
Screen

I/System.out(
I/System.out(
I/System.out(
I/System.out(
I/System.out(

279): >>>>>>>>>>>> ClockPanel.notifyClockPrefsChanged() <<<<<<<<<<<
279): >>>>>>>>>>>> CoolClock.onResume() <<<<<<<<<<<
279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<< ( called by CoolClock )
279): ClockRunThread threadState: Running
279): ClockRunThread threadState: Running

Press Back
Button to show
Home Screen

I/System.out(
I/System.out(
I/System.out(
I/System.out(
I/System.out(

279): >>>>>>>>>>>> CoolClock.onPause() <<<<<<<<<<<
279): >>>>>>>>>>>> ClockPanel.pause() <<<<<<<<<<<
279): >>>>>>>>>>>> CoolClock.onStop() <<<<<<<<<<<
279): >>>>>>>>>>>> ClockPanel.stop() <<<<<<<<<<<
279): >>>>>>>>>>>> CoolClock.onDestroy() <<<<<<<<<<<
Copyright (c) Richard Creamer 2011 - All Rights Reserved

When Main Menu is
displayed, clock run
thread remains active

After any onPause() event,
clock run thread is suspended
and is not consuming CPU
time for drawing
After PrefsActivity has
exited, clock is again
visible and run thread
state is active

16
How Cool Clock Responds to State Changes
Android Event Stream
App Launch

• Android starts CoolClock Activity
• CoolClock.onCreate()
• CoolClock.onStart()
• CoolClock.onResume()
• Clock now Running and visible

Press phone
menu button

• CoolClock.onCreateOptionsMenu()
• Clock run thread still Running

Press Settings
button on Main
Menu

• Android starts PrefsActivity
• CoolClock.onOptionsMenuClosed()
• CoolClock.onPause()
• PrefsAcvitivity is made visible
• Clock is now behind Settings screen
• Clock run thread is Suspended

• Instantiates ClockPanel
• Calls Activity.setContentView( clockPanel )

User exits
PrefsActivity

User presses
phone Back
button

• ClockPanel .onActivityResult ()
•ClockPanel.notifyClockPrefsChange()
• CoolClock.onResume()
• Clock now Running and visible again

• CoolClock.onPause()
• CoolClock.onStop()
• CoolClock.onDestroy()
• CoolClock now destroyed

• Calls ClockPanel.resume():
• Sets clock run thread state to Running

• Calls ClockPanel.pause():
• Sets clock run thread state to Suspended
• Calls ClockPanel.notifyClockPrefsChange():
• Reloads user preference values from Android
• Redraws clock with updated preferences

• Calls ClockPanel.resume():
• Sets clock run thread state to Running
• Calls ClockPanel.pause():
• Sets clock run thread state to Suspended
• Calls ClockPanel.stop():
• Stops clock run thread
17

Copyright (c) Richard Creamer 2011 - All Rights Reserved
How to reference Android resources

18
How Android resources are referenced
1

From Java

2

From XML

Recall resource types include

1

Resources
• drawable ( bitmaps, icons )
• layout ( Activities )
• menu ( menu content )
• values ( strings, arrays )
• xml ( Preference screen layout & content, etc. )

Excerpt from CoolClock.java
public boolean onCreateOptionsMenu( Menu menu ) {
super.onCreateOptionsMenu( menu );
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.menu, menu );
return true;
}

Recall that R is an
auto-generated
Java class

2

From Java

Clockresmenumenu.xml

From XML

Excerpt from Clockresxmlsettings.xml
<CheckBoxPreference
android:key="hide_circular_arrows"
android:title="@string/hide_circular_arrows_title"
android:summary="@string/hide_circular_arrows_summary"
android:defaultValue="true" />

Clockresvaluesstrings.xml Excerpt
<string name="hide_circular_arrows_title">Hide Circular Arrows</string>
19
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Creating Main Menus

20
Creating Main Menus
CoolClock.java:

2

Handle Main
Menu Creation

1

Define Main
Menu GUI

@Override
public boolean onCreateOptionsMenu( Menu menu ) {
super.onCreateOptionsMenu( menu ); // Call base class first
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.menu, menu );
return true;
}
CoolClock.java:

3

Handle Main Menu
Button Presses

Clockresmenumenu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/settings"
android:title="@string/settings_label"
android:alphabeticShortcut="@string/settings_shortcut" />
<item android:id="@+id/help"
android:title="@string/help_option"
android:alphabeticShortcut="@string/help_shortcut" />
<item android:id="@+id/about"
android:title="@string/about_label"
android:alphabeticShortcut="@string/about_shortcut" />
<item android:id="@+id/exit"
android:title="@string/exit_label"
android:alphabeticShortcut="@string/exit_shortcut" />
</menu>

@Override
public boolean onOptionsItemSelected( MenuItem mi ) {
super.onOptionsItemSelected( mi ); // Called first
switch ( mi.getItemId() ) {
case R.id.help:
startActivity( new Intent( this, HelpActivity.class ) );
return true;
case R.id.settings:
startActivityForResult( new Intent( this, PrefsActiviy.class ), PREFS_ACTIVITY_ID );
return true;
case R.id.about:
startActivity( new Intent( this, AboutActivity.class ) );
return true;
About Menu “Inflation”
case R.id.exit:
In this line of code:
finish();
inflater.inflate( R.menu.menu, menu );
return true;
The MenuInflater reads menu.xml and then
}
programmatically adds the MenuItems to
return false;
the method argument: menu
}

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Result:
Main Menu Screenshot

21
Introduction to Android Preferences

22
Android Preferences Overview
• Android includes an easy-to-use Preferences sub-system that allows end users to
customize an application’s settings.
• Preferences are roughly equivalent to ToolsOptions for desktop applications.

Pressing the Settings
button displays the
Preferences/Settings
menu

The main menu is
displayed when the
user presses the
device’s Menu button

The Preferences screen
GUI layout/content is
defined in settings.xml
( see slide 25 )
23
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Android Preferences
• The Android Preferences sub-system provides:
• XML-based declarative GUI layout definition of Preferences menus/screens
• Automatic launching of Preferences screens and their lifecycle management
• Transparent persistence of user changes
• Easy-to-use API for retrieving persisted Preferences parameter values
• The Cool Clock Preferences screens:
Primary Preferences
Screen

Clock Mode Options

Minute Display Mode
Options

Hand Linked to Circular
Arrows Options

24
Copyright (c) Richard Creamer 2011 - All Rights Reserved
settings.xml
( Defines Preferences GUI layout and content* )
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">

<CheckBoxPreference
android:key="hide_sec_hand"
android:title="@string/hide_sec_hand_title"
android:summary="@string/hide_sec_hand_summary"
android:defaultValue="true" />
<CheckBoxPreference
android:key="hide_circular_arrows"
android:title="@string/hide_circular_arrows_title"
android:summary="@string/hide_circular_arrows_summary"
android:defaultValue="true" />

<CheckBoxPreference
android:key="hide_digital_time"
android:title="@string/hide_digital_time"
android:summary="@string/hide_digital_time_summary"
android:defaultValue="true" />
<CheckBoxPreference
android:key="hide_phrase_time"
android:title="@string/hide_phrase_time"
android:summary="@string/hide_phrase_time_summary"
android:defaultValue="true" />

<ListPreference
android:key="clock_modes"
android:title="@string/clock_mode_label"
android:summary="@string/clock_mode_summary"
android:defaultValue="@string/clock_mode_label"
android:entries="@array/clock_modes"
android:entryValues="@array/clock_mode_values" />

<ListPreference
android:key="minute_display_modes"
android:title="@string/minute_display_mode_title"
android:summary="@string/minute_display_mode_summary"
android:defaultValue="@string/every_five_minutes"
android:entries="@array/minute_display_modes"
android:entryValues="@array/minute_display_mode_values" />
<ListPreference
android:key="arc_modes"
android:title="@string/arc_mode_label"
android:summary="@string/arc_mode_summary"
android:defaultValue="@string/arc_mode_minutes"
android:entries="@array/arc_modes"
android:entryValues="@array/arc_modes_values" />
</PreferenceScreen>

*Multiple-choice widget values are stored in resvaluesarrays.xml

25
Copyright (c) Richard Creamer 2011 - All Rights Reserved
How Preferences Menus Work - Details
User

Presses

In main activity ( CoolClock.java )
public boolean onCreateOptionsMenu( Menu menu ) {
super.onCreateOptionsMenu( menu );
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.menu, menu );
return true;
}

Calls (1st time)

Android

( R.menu.menu )

Phone Menu button

Displays

Android

Main menu

Presses

User

Settings button

Calls

Android

Time
Displays

Android
User

The “inflater” reads the defined main menu items
defined in menu.xml and programmatically adds these
menu items to the method argument’s Menu object

Preferences screen

Interacts with

User
Android

Closes

In main activity ( CoolClock.java )
public boolean onOptionsItemSelected( MenuItem mi ) {
switch ( mi.getItemId() ) {
... cases for other main menu items ...
case R.id.settings:
startActivityForResult( new Intent( this, PrefsActivity.class ), PREFS_ACTIVITY_ID );
return true;
...
Tells Android to launch
}
Preferences screen
return false;
}

Preferences screen

Preferences screen
Calls

In main activity ( CoolClock.java )
protected void onActivityResult( int requestCode, int resultCode, Intent data ) {
if ( requestCode == PREFS_ACTIVITY_ID ) {
Reads updated Preferences
final ClockState newCs = PrefsActivity.getStoredPrefs( this );
clockPanel.notifyClockPrefsChanged( newCs );
Notifies ClockPanel of user
}
Preferences state changes
}
26

Copyright (c) Richard Creamer 2011 - All Rights Reserved
Retrieving Preferences Values
• Stored Preferences values are read using { ParamKey, DefaultValue } pairs.
• Example of how to retrieve Android-persisted Preferences values:
PrefsActivity.java ( excerpts )
public final class PrefsActivity extends PreferenceActivity {
public static final boolean HIDE_SECOND_HAND_DEFAULT = true;

Default parameter value

// Key values for accessing parameters from standard Android Preferences/Settings sub-system
private static final String HIDE_SEC_HAND_KEY = "hide_sec_hand";
Parameter key
private static final String ARC_MODES_KEY = "arc_modes";
@Override
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
addPreferencesFromResource( R.xml.settings );
}
Reference to settings.xml
...retrieving Preferences parameter values...
// Get a SharedPreferences object
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( cc );

{ ParamKey, DefaultValue } pair

// Grab actual Preferences/Settings VALUES
boolean hideSecondHand = sp.getBoolean( HIDE_SEC_HAND_KEY, HIDE_SECOND_HAND_DEFAULT );
String arcModeString = sp.getString( ARC_MODES_KEY, defaultArcModeMenuString );
}
27
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Creating Simple TextView Based Activities

28
About Panel

Creating a Simple TextView Activity
1 Define GUI layout

about.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip">
<TextView
android:id="@+id/about_content"
About panel’s
android:layout_width="wrap_content"
text content
android:layout_height="wrap_content"
android:text="@string/about_text" />
</ScrollView>

Note nesting of GUI
components

android.widget.TextView
TextView extends android.view.View

2 Define activity class

AboutActivity.java
package net.maxuint32.coolapps;
import android.app.Activity;
import android.os.Bundle;

Activity’s GUI layout

public class AboutActivity extends Activity {
@Override
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.about );
}
}
strings.xml (excerpt)
<string name="about_text">
CoolClock v0.8n
Copyright (c) Richard Creamer 2011n
All Rights Reservedn
Email: maxuint32.coolapps@gmail.comnn
CoolClock can run in three modes:nn
1. Learn Mode:nn
This mode helps teach children how to read an analog clock.nn
Children can drag clock hands and watch:nn
- The other hands moven
- How the digital time changesn
- How the "phrase" time changesn
- "Before" and "after" circular arrowsnn
2. Clock Mode:nn
In this mode, CoolClock is just a normal analog clock - it displays the current time and
runs.nn
3. Modern Clock:nn
In this mode, a more modern clock is drawn, without numbers or minute ticks.
</string>

3

Start
activity

CoolClock.java (excerpt)
@Override
public boolean onOptionsItemSelected( MenuItem mi ) {
...
switch ( mi.getItemId() ) {
case R.id.about:
startActivity( new Intent( this, AboutActivity.class ) );
return true;
}
....
Here we are starting the
return false;
}
AboutActivity when a main

4 Define text content

Copyright (c) Richard Creamer 2011 - All Rights Reserved

menu item is pressed, but
Activities can be started at
any time.

29
Custom 2D Graphical Components

30
A Basic Custom 2D Graphics App/Component
• GraphicsTest1 is a very simple custom-drawn 2D component
• It draws a gradient background with a random number of spheres of random color and opacity
• It ignores many best practices such as avoiding expensive computations in the GUI thread

31
Copyright (c) Richard Creamer 2011 - All Rights Reserved
GraphicsTest1 - Salient Java Source Code Elements
package ...
import ...

Steps for creating a custom 2D component Activity

1.
2.
3.
4.

public class GraphicsTest1 extends Activity {
... attributes...
@Override
public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
requestWindowFeature( Window.FEATURE_NO_TITLE );
setContentView( new GraphicsView( this ) );
}
4

Create new class which extends android.view.View
New class must override onDraw()
onDraw() does all drawing
Call Activity.setContentView( custom 2D component )

GraphicsTest1 sets
its content view to
an instance of
GraphicsView

// Nested class that fills its bounds with random spheres
private static class GraphicsView extends View {

1

... attributes...
public GraphicsView( Context context ) {
super( context );
}

2

GraphicsView
overrides
onDraw()

@Override
protected void onDraw( Canvas canvas ) {
// Do all drawing
Do all drawing:
}

} // End nested GraphicsView class

background +
spheres/graphics

3

Note: The GraphicsView class does not have
to be a nested class - it was only done this
way so the entire Activity + View would fit
into a single Java file...

} // End GraphicsTest1 class
32
Copyright (c) Richard Creamer 2011 - All Rights Reserved
GraphicsTest1 - Complete Java Source Code
package org.example.graphics;
import java.util.Random;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
public class GraphicsTest1 extends Activity {

3
Do all drawing:
gradient paint
background +
spheres

GraphicsTest1
sets its content
view to be an
instance of
GraphicsView

@Override
public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
requestWindowFeature( Window.FEATURE_NO_TITLE );
setContentView( new GraphicsView( this ) );
}

4

private void drawRandSpheresAndBkg( Canvas canvas ) {
int numCircles = rand.nextInt( maxCircles );
if ( numCircles < minCircles ) numCircles = minCircles;
int w = this.getWidth();
int h = this.getHeight();
Paint bkgPaint = new Paint();
bkgPaint.setShader( new LinearGradient( 0, 0, w, h, c1, c2, Shader.TileMode.MIRROR ) );
canvas.drawPaint( bkgPaint ); // This fills Canvas with background gradient paint
for ( int i = 0; i < numCircles; ++i ) {
int xc = rand.nextInt( w );
Such methods can be computationally
int yc = rand.nextInt( h );
expensive and should not be done in the
int r = rand.nextInt( ( w + h )/16 );
main GUI thread! Run expensive tasks
int red = rand.nextInt( 255 );
in another thread! Then, post results
int green = rand.nextInt( 255 );
back to GUI thread using a Handler.
int blue = rand.nextInt( 255 );
int alpha = rand.nextInt( 255 );
int color = Color.argb( alpha, red, green, blue );
drawSphereImage( canvas, xc, yc, r, color ); // Draw the sphere
}
}

public void drawSphereImage( Canvas canvas, float xc, float yc, float radius, int color ) {
if ( radius < 3.0f ) radius = 3.0f;
// Nested class that fills its bounds with random spheres
Paint paint = new Paint( Paint.ANTI_ALIAS_FLAG );
Class should
final float highlightOffsetInPct = 0.4f;
1
private static class GraphicsView extends View {
extend View
float highlightOffset = highlightOffsetInPct * radius;
float highlightXc = xc - highlightOffset;
private final int maxCircles = 150, minCircles = 5;
float highlightYc = yc - highlightOffset;
private final int c1 = 0xff4466aa, c2 = 0xff112244;
final float radiusScaleFactor = 3;
private final Random rand = new Random( System.currentTimeMillis() );
float highlightRadius = radiusScaleFactor * highlightOffset;
RadialGradient rg = new RadialGradient( highlightXc, highlightYc,
public GraphicsView( Context context ) {
highlightRadius, 0xffffffff, color, Shader.TileMode.MIRROR );
super( context );
GraphicsView
paint.setShader( rg );
}
overrides
canvas.drawCircle( xc, yc, radius, paint );
onDraw()
2
}
@Override
} // End GraphicsView class
protected void onDraw( Canvas canvas ) {
} // End GraphicsTest1 class
drawRandSpheresAndBkg( canvas );
}

33
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Custom 2D View Components - Overview
Creating the basic GraphicsTest1 class was pretty simple:
• Create a class that extends android.view.View ( GraphicsView )
• GraphicsView must override View.onDraw( Canvas canvas )
• In onDraw(), perform all the necessary drawing ( images, shapes, primitives )
• Main Activity calls setContentView() with an instance of GraphicsView

When creating non-trivial components, other details become important, including:
• Never allow direct access to a GUI’s state from a non-GUI thread ( thread-safe design )
• Appropriately respond to lifecycle events
• Use background/worker threads to perform long-running, computationally-expensive tasks
• Methods to return results from non-GUI threads to the main GUI thread include:
• Call Activity.runOnUiThread( Runnable )
• Call the android.os.Handler.post( Runnable ) method of a Handler object
• The Handler must be instantiated in the GUI thread
• If user touch input is required the View-derived class should:
• Implement the OnTouchListener interface
• Implement View.onTouch()
• Handle all touch events and object hit testing if applicable
• Minimize use of system resources
• Call base class methods at the appropriate point when overriding methods
34
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Handling Touch Events

35
Implement OnTouchListener.onTouch()
public boolean onTouch( View v, MotionEvent me ) {
if ( curPrefs.clockOperationMode != E.ClockOperationMode.LearnMode )
return false;
if ( !curPrefs.enableHandDragging || me.getPointerCount() > 1 ) // Only handle 1 finger at this time
return false;
float x = me.getX();
float y = me.getY();
int action = me.getAction() & MotionEvent.ACTION_MASK;

ClockPanel implements the
OnTouchListener interface in order to
receive touch input

switch ( action ) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
E.ClockHands hand = isCloseToHand( x, y );
if ( hand == E.ClockHands.None )
return false;
inDrag = true;
dragHand = hand;
return true;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
inDrag = false;
dragHand = E.ClockHands.None;
return true;
case MotionEvent.ACTION_MOVE:
processMove( x, y );
return true;

Begin a hand drag...

Terminate a hand drag...

Move the current drag hand...

}
return false;
}

36
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Graphics techniques used in Cool Clock

37
Cool Clock’s Graphics Layers
Background
LinearGradient shader
mirroring = true

Fill clock oval

Fill clock oval, clipped,
with LinearGradient
shader

Add hour and minute
numbers + minute ticks

( This is what is cached )

• Hands (LinearGradient shader)
• Hands are drawn via Path objects
• Center dot (RadialGradient shader)

Upper-left highlight
(LinearGradient)

Lower-right highlight
(LinearGradient)

Digital time,
Phrase time

• Circular arrows fill/edges
• Circular arrow labels

Copyright (c) Richard Creamer 2011 - All Rights Reserved

38
Utility Class: Vector2d (selected methods)
//Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved

Complete Vector2d public interface:

package net.maxuint32.coolapps;
import android.graphics.PointF;

public float x, y;
public final class Vector2d {
public Vector2d();
public float x, y;
public Vector2d( final float x, final float y );
public Vector2d( final float x, final float y ) {
public Vector2d( final Vector2d v );
this.x = x;
public Vector2d( final PointF p1, final PointF p2 );
this.y = y;
public Vector2d setComponents( final float x,final float y );
}
public float length();
public synchronized Vector2d rotateClockwise( final float angle ) {
public float dotProduct( final Vector2d v );
rotateCounterClockwise( -angle );
public float cosAngle( final Vector2d v );
return this;
public Vector2d zero();
}
public Vector2d add( final Vector2d v );
public synchronized Vector2d rotateCounterClockwise( final float angle ) {
float cos = ( float ) Math.cos( angle );
public Vector2d mult( final float factor );
float sin = ( float ) Math.sin( angle );
public Vector2d div( final float quotient );
CG Rotation Matrix
float newX = cos * x + sin * y;
public Vector2d rotateClockwise( final float angle );
float newY = -sin * x + cos * y;
x’
x
public Vector2d rotateCounterClockwise( final float angle );
cos ѳ sin ѳ
x = newX;
public Vector2d setLength( final float length );
y’
-sin ѳ cos ѳ y
y = newY;
public Vector2d normalize();
return this;
public Vector2d parallelOfLength( final float length );
}
public synchronized Vector2d setLength( final float length ) {
normalize();
x = x * length;
y = y * length;
return this;
}
public synchronized Vector2d normalize() {
float length = length();
if ( length == 0f ) throw new IllegalStateException( "Vector length is zero! Cannot divide by zero!" );
x = x / length;
y = y / length;
return this;
}
public synchronized float length() {
return ( float ) Math.sqrt( x * x + y * y );
}
39
}
Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved

=
Drawing Shapes Using Paths
1.
2.
3.
4.
5.
6.

Create a Path object p ( see below for details )
Call p.moveTo() for starting point 0
Call p.lineTo() or p.arcTo() points 1 - 9 in clockwise order
Call p.close()
Configure canvas paint or shader ( can be fill or stroke mode )
Call canvas.drawPath( p )

0

1

2

3

4

8

7
9
private Path getRhsArrow( float a1, float a2 ) { // Almost complete & accurate...
Yellow arrows
are Vector2d
float r1 = Innermost arrowhead side point radius
6
rotation angles
float r2 = Annulus inner radius
5
float r3 = Annulus center radius
float r4 = Annulus outer radius
float r5 = Outermost arrowhead side point radius
float deltaAInDeg = toDegrees( F.abs( a1 - a2 ) );
( xc, yc )
Use Vector2d class
Path p = new Path();
to handle math
Vector2d v = new Vector2d( 0, -r3 );
( previous slide )
p.moveTo( xc + v.x, yc + v.y );
v.rotateClockwise( arrowheadAngle ).setLength( r5 );
p.lineTo( xc + v.x, yc + v.y );
 method = Path methods
v.setLength( r4 );
 method = Vector2d methods
p.lineTo( xc + v.x, yc + v.y );
p.arcTo( new RectF( xc - r4, yc - r4, xc + r4, yc + r4 ), -90 + arrowheadAngle, deltaAInDeg - 2f * arrowheadAngle );
p.lineTo( xc + v.x, yc + v.y );
v.rotateClockwise (arrowheadAngle ).setLength( r3 ); // We can chain method calls because most Vector2d methods return this
p.lineTo( xc + v.x, yc + v.y );
v.rotateCounterClockwise( arrowheadAngle ).setLength( r1 );
p.lineTo( xc + v.x, yc + v.y );
v.setLength( r2 );
p.lineTo( xc + v.x, yc + v.y );
p.arcTo( new RectF( xc - r2, yc - r2, xc + r2, yc + r2 ), -90 + deltaAInDeg - arrowheadAngle, -( deltaAInDeg - 2f * arrowheadAngle ) );
v.rotateCounterClockwise( F.abs( a1 - a2 ) - 2 * arrowheadAngle ).setLength( r1 );
p.lineTo( xc + v.x, yc + v.y );
p.close();
return p;
40
}
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Drawing Text

41
The FontMetrics Class and Paint.getTextBounds()
• Android provides the android.graphics.Paint.FontMetrics class:
• Provides Typeface dimensions useful for correctly positioning text
• FontMetrics class attributes:
• ascent - “Recommended” distance above baseline
• bottom - Maximum distance below the base line for lowest glyph
• descent - “Recommended” distance below baseline
• leading - “Recommended” space between lines of text
• top - Maximum distance above baseline for tallest glyph
• Important:
• Attribute values pertaining to distance above the baseline are NEGATIVE
Most Useful
Attributes

(-) top
baseline

Glyph
( bottom - top )

bottom
• Note: Paint.getTextBounds() often provides sufficient information
( FontMetrics object not always needed - see following examples... )

42
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Drawing Text - Code
General-purpose text drawing method supporting horizontal and vertical alignment
public enum TextVertAlign { Top, Middle, Baseline, Bottom }; // Enumeration representing vertical alignment positions
public static void drawHvAlignedText( Canvas canvas, float x, float y, String s, Paint p, Paint.Align horizAlign, TextVertAlign vertAlign ) {
// Set horizontal alignment
p.setTextAlign( horizAlign );

// Get bounding rectangle which we’ll need below...
Rect r = new Rect();
p.getTextBounds( s, 0, s.length(), r ); // Note: r.top will be negative
// Compute y-coordinate we'll need for drawing text for specified vertical alignment
float textX = x;
float textY = y;
switch ( vertAlign ) {
Also, look at:
case Top:
• Paint.measureText()
textY = y - r.top; // Recall that r.top is negative
• Paint.getFontMetrics()
break;
• android.text.StaticLayout
case Middle:
• android.widget.FrameLayout
textY = y - r.top - r.height()/2;
break;
case Baseline: // Default behavior - no changes to y-coordinate
break;
case Bottom:
textY = y - ( r.height() + r.top );
break;
}
canvas.drawText( s, textX, textY, p ); // Now we can draw the text with the proper ( x, y ) coordinates
}
43
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Text Drawing Example
// Excerpts from TextDrawing demo...
public enum TextVertAlign { Top, Middle, Baseline, Bottom };
private float fontScaleFactor = 0.08f; // Crude scaling to display
private float crosshairScaleFactor = 0.05f; // Crude scaling to display
private int numTextColumns = Paint.Align.values().length;
private int numTextRows = TextVertAlign.values().length;
private float crosshairSize = 10f;
@Override
protected void onDraw( Canvas canvas ) {
canvas.drawPaint( bkgPaint ); // Clear background
// Compute some variables we'll need
float w = getWidth();
float h = getHeight();
crosshairSize = ( w < h ) ? w * crosshairScaleFactor : h * crosshairScaleFactor;
float textSize = ( w < h ) ? w * fontScaleFactor : h * fontScaleFactor;
textPaint.setTextSize( textSize );
float colWidth = ( w - 4 * crosshairSize ) / ( numTextColumns - 1 );
float lineSep = h / ( numTextRows + 1f );
float x = 2f * crosshairSize;

// Loop over horizontal and vertical alignment enum values
// Draw string using loop values for horiz and vert alignment
for ( Paint.Align ha : Paint.Align.values() ) {
float y = lineSep;
for ( TextVertAlign va : TextVertAlign.values() ) {
drawHvAlignedText( canvas, x, y, s, textPaint, ha, va );
drawCrosshairs( canvas, x, y );
y += lineSep;
}
x += colWidth;
}

Left

Center

Right

Top

Middle

Baseline

Bottom

}
Copyright (c) Richard Creamer 2011 - All Rights Reserved

44
Optimization &Threading Considerations

45
Optimization
• Because onDraw() is called frequently and because much of the Cool Clock graphics drawing
is repetitive and computationally expensive, two techniques were used to reduce CPU usage
and to maintain a responsive GUI:
• Cache the screen bitmap ( background + clock face + ticks + numbers )
• The clock face only changes if:
• The user modifies certain Preferences settings
• The device orientation changes
• As a result, the background and clock face are computed once into cached
bitmaps, one for each orientation, until Preferences changes require updates.
• Each time onDraw() is called, a cached bitmap is efficiently copied to the screen,
then only the hands, circular arrows, highlights, and time text fields are drawn.
• Compute the screen bitmap in a background thread:
• To keep the GUI responsive, the long-running task of drawing the background and
clock face are performed in a background thread - not in the GUI thread.
• While a bitmap is not ready, onDraw() simply fills the screen with the background
paint.
• When the bitmap computation is completed and the background worker thread
terminates, a new Runnable is posted to the GUI Handler which adopts the new
Bitmap and then calls invalidate().
• invalidate() queues up a future call to onDraw()
46
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Skeleton Definition of Background Worker Thread
private static class ClockFaceRendererTask extends Thread {
// Fields containing final defensive copies of a snapshot of ClockPanel fields required for rendering clock face bitmap
...
private final Bitmap bm; // The output of this worker thread
public ClockFaceRendererTask( final ClockPanel clockPanel ) {
// Make final defensive copies of ClockPanel state data
bm = rp.bm.getAndSet( null ); // Use AtomicReference<Bitmap> to atomically assume exclusive ownership of Bitmap
}
public void run() {
// Validate remaining input data
renderClockFace(); // Thread terminates when run() completes
}
public Bitmap getFinalBitmap() {
// Ensure renderingCompleted == true
return bm;
}
private void renderClockFace() {
// Draw background, clock face
// Draw numbers, ticks
renderingCompleted = true;
}
private void drawNumbers( Canvas canvas, float xc, float yc, float radius ) {
...
}
private void drawTicks( Canvas canvas, float xc, float yc, float radius ) {
...
}
}

47
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Invoking the ClockFaceRendererTask
• In order to create and run a clock renderer task in a background thread, we need to create an outer
“monitor” thread to:
• Instantiate and start a renderer task
• Wait for the renderer sub-task to complete
• After the renderer has finished computing a new bitmap, we need to define and post a Runnable
task to the GUI handler which will run in the GUI thread and safely adopt the new Bitmap “later”
• This is a bit complex for those new to threads ( consult the books listed on slide 11 for more details )
• Below is a diagram of what the next page’s callRenderer() method is doing...
Non-GUI Worker Thread

1

callRenderer()

Defines complex thread

Instantiates ClockFaceRendererTask

Starts the ClockFaceRendererTask

Waits for the ClockFaceRendererTask to
complete/terminate

Defines a Runnable which must be run in the
GUI thread which adopts the newly
computed Bitmap

2

callRenderer()

Starts outer thread and
returns immediately

Posts the above Bitmap adoption Runnable
to the GUI Handler which will run “later”

48
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Invoking the ClockFaceRendererTask
A worker thread launched and managed from within an outer monitor thread
private void callRenderer() { // Called only in onDraw() - returns immediately after defining and starting background worker thread
final ClockPanel cp = this;
Thread t = new Thread( new Runnable() {

Define an outer thread to run in background which, in turn,
starts and monitors a nested ClockFaceRendererTask thread

1

public void run() {

2

clockFaceRenderer = new ClockFaceRendererTask( cp ); // ClockFaceRendererTask extends Thread
clockFaceRenderer.start(); // Start background renderer task asynchronously ( not in GUI thread )
// Wait for the renderer thread to terminate
while ( true ) {
try {
clockFaceRenderer.join(); // join() waits for thread to terminate
break;
}
catch ( InterruptedException ie ) {
}
}

Instantiate and start renderer task thread

Outer thread waits for renderer
thread to complete

3

// New bitmap now computed - now have GUI thread adopt new bitmap reference and redraw itself
final Bitmap newBitmap = clockFaceRenderer.getFinalBitmap();
if ( newBitmap == null ) throw new IllegalStateException( "Null bitmap returned from ClockFaceRendererTask!" );
guiThreadHandler.post( new Runnable() {
public void run() {
RenderParams rp = getCurRp();
Outer thread posts a sub-task to GUI Handler to
rp.bm.set( newBitmap );
4
adopt newly computed bitmap which will run
computingBitmap = false;
“at a later time” in the GUI thread
invalidate();
}
} );

}
} ); // End outer thread definition
// Start the thread we just defined - then return immediately
t.start();
}

After defining the outer thread, we
start it and return immediately

Copyright (c) Richard Creamer 2011 - All Rights Reserved

5

49
The Builder Pattern

50
Builder Pattern Use
• The ClockPanel attributes can be initialized in many different ways depending on use context.
• As a result, providing a Builder pattern to construct ClockPanel objects was implemented.
• The main CoolClock Activity creates its ClockPanel component using this code:
ClockState cs = PrefsActivity.getStoredPrefs( this ); // Get settings from previous Cool Clock execution
clockPanel = new ClockPanel.ClockPanelBuilder(). // Construct a Builder object
context( this ).
// Set desired field values
hideSecondHand( cs.hideSecondHand ).
Note “dot”
hideArcs( cs.hideCircularArrows ).
operator use
hideDigitalTime( cs.hideDigitalTime ).
Reinstate the previous
hideTextTime( cs.hidePhraseTime ).
field values, then call
clockOperationMode( cs.clockOperationMode ).
buildClockPanel() to
minuteNumDisplayMode( cs.minuteNumDisplayMode ).
construct ClockPanel
arcMode( cs.arcMode ).
instance.
buildClockPanel(); // Ask Builder to construct a ClockPanel object

Calls private
ClockPanel
constructor

51
Copyright (c) Richard Creamer 2011 - All Rights Reserved
The ClockPanel Constructor
The only ClockPanel constructor accepts a ClockPanelBuilder argument:
private ClockPanel( ClockPanelBuilder cpb ) {
super( cpb.context ); // Call base class
this.curPrefs = cpb.cs; // Data class encapsulating Preferences settings
rpLand.orientation = E.Orientation.Landscape; // Data class encapsulating drawing geometry params
rpPort.orientation = E.Orientation.Portrait; // Separate RenderParam object for both orientations
guiThreadHandler = new Handler(); // Create Handler in the GUI thread
curPrefs.enableHandDragging = ( curPrefs.clockOperationMode == E.ClockOperationMode.LearnMode );
setOnTouchListener( this ); // Subscribe to touch events
}

52
Copyright (c) Richard Creamer 2011 - All Rights Reserved
Builder Pattern Implementation
public static class ClockPanelBuilder { // Nested static class in ClockPanel.java

public ClockPanelBuilder hidePhraseTime( boolean hidePhraseTime ) {
cs.hidePhraseTime = hidePhraseTime;
return this;
}

// Reference to CoolClock Activity ( needed by ClockPanel )
private Context context;
// Start with default clock param values
public ClockPrefs cs = ClockPrefs.defaultClockPrefs();

public ClockPanelBuilder hideCircularArrows( boolean hideCircularArrows ) {
cs.hideCircularArrows = hideCircularArrows;
return this;
}

public ClockPanelBuilder() { }
public ClockPanelBuilder context( Context context ) {
this.context = context;
return this;
}

public ClockPanelBuilder minuteNumDisplayMode(
E.MinuteNumDisplayMode minuteNumDisplayMode ) {
cs.minuteNumDisplayMode = minuteNumDisplayMode;
return this;
}

public ClockPanelBuilder hideHourHand( boolean hideHourHand ) {
cs.hideHourHand = hideHourHand;
return this;
}

public ClockPanelBuilder arcMode( E.ArcMode arcMode ) {
cs.arcMode = arcMode;
return this;
}

public ClockPanelBuilder hideMinuteHand( boolean hideMinuteHand ) {
cs.hideMinuteHand = hideMinuteHand;
return this;
}

public ClockPanelBuilder digitalTimeMode(
E.DigitalTimeMode digitalTimeMode ) {
cs.digitalTimeMode = digitalTimeMode;
return this;
}

public ClockPanelBuilder hideSecondHand( boolean hideSecondHand ) {
cs.hideSecondHand = hideSecondHand;
return this;
}

public ClockPanelBuilder clockOperationMode(
E.ClockOperationMode clockOperationMode) {
cs.clockOperationMode = clockOperationMode;
return this;
}

public ClockPanelBuilder enableHandDragging( boolean enableHandDragging ) {
cs.enableHandDragging = enableHandDragging;
return this;
}

public ClockPanel buildClockPanel() {
if ( context == null )
throw new IllegalStateException(
"Context field must be non-null before calling buildClockPanel()!" );
return new ClockPanel( this ); // Call private constructor
}

public ClockPanelBuilder hideDigitalTime( boolean hideDigitalTime ) {
cs.hideDigitalTime = hideDigitalTime;
return this;
}
}

Copyright (c) Richard Creamer 2011 - All Rights Reserved

53
Cool Clock
Helping children to read analog clocks

An introduction to developing custom 2D graphics Android applications
Richard Creamer
2to32minus1 @gmail.com

Links
Home Page
LinkedIn Profile
Android Market Cool Clock Product Listing
External Cool Apps Software Website
External Cool Clock Product Details Page

54
Copyright (c) Richard Creamer 2011 - All Rights Reserved

Weitere ähnliche Inhalte

Ähnlich wie Case Study: Cool Clock - An Intro to Android Development

Android dev o_auth
Android dev o_authAndroid dev o_auth
Android dev o_authlzongren
 
Android Training in Chandigarh
Android Training in ChandigarhAndroid Training in Chandigarh
Android Training in ChandigarhArcadian Learning
 
Android programming Assignment Help
Android programming Assignment HelpAndroid programming Assignment Help
Android programming Assignment Helpsmithjonny9876
 
Matteo Gazzurelli - Introduction to Android Development - Have a break edition
Matteo Gazzurelli - Introduction to Android Development - Have a break editionMatteo Gazzurelli - Introduction to Android Development - Have a break edition
Matteo Gazzurelli - Introduction to Android Development - Have a break editionDuckMa
 
Overview of Adroid Architecture.pptx
Overview of Adroid Architecture.pptxOverview of Adroid Architecture.pptx
Overview of Adroid Architecture.pptxdebasish duarah
 
Android operating system
Android operating systemAndroid operating system
Android operating systemDev Savalia
 
Introduction to android sessions new
Introduction to android   sessions newIntroduction to android   sessions new
Introduction to android sessions newJoe Jacob
 
Introduction to Android- A session by Sagar Das
Introduction to Android-  A session by Sagar DasIntroduction to Android-  A session by Sagar Das
Introduction to Android- A session by Sagar Dasdscfetju
 
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013DuckMa
 
Android development
Android developmentAndroid development
Android developmentmkpartners
 
.NET? MonoDroid Does
.NET? MonoDroid Does.NET? MonoDroid Does
.NET? MonoDroid DoesKevin McMahon
 

Ähnlich wie Case Study: Cool Clock - An Intro to Android Development (20)

Android course1
Android course1Android course1
Android course1
 
Introduction to android
Introduction to androidIntroduction to android
Introduction to android
 
Android dev o_auth
Android dev o_authAndroid dev o_auth
Android dev o_auth
 
Android Training in Chandigarh
Android Training in ChandigarhAndroid Training in Chandigarh
Android Training in Chandigarh
 
Android course (lecture1)
Android course (lecture1)Android course (lecture1)
Android course (lecture1)
 
Android programming Assignment Help
Android programming Assignment HelpAndroid programming Assignment Help
Android programming Assignment Help
 
Matteo Gazzurelli - Introduction to Android Development - Have a break edition
Matteo Gazzurelli - Introduction to Android Development - Have a break editionMatteo Gazzurelli - Introduction to Android Development - Have a break edition
Matteo Gazzurelli - Introduction to Android Development - Have a break edition
 
Introduction to android
Introduction to androidIntroduction to android
Introduction to android
 
Overview of Adroid Architecture.pptx
Overview of Adroid Architecture.pptxOverview of Adroid Architecture.pptx
Overview of Adroid Architecture.pptx
 
My androidpresentation
My androidpresentationMy androidpresentation
My androidpresentation
 
Android operating system
Android operating systemAndroid operating system
Android operating system
 
Introduction to android sessions new
Introduction to android   sessions newIntroduction to android   sessions new
Introduction to android sessions new
 
Chapter1
Chapter1Chapter1
Chapter1
 
Introduction to Android- A session by Sagar Das
Introduction to Android-  A session by Sagar DasIntroduction to Android-  A session by Sagar Das
Introduction to Android- A session by Sagar Das
 
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
 
Android development
Android developmentAndroid development
Android development
 
.NET? MonoDroid Does
.NET? MonoDroid Does.NET? MonoDroid Does
.NET? MonoDroid Does
 
Android OS
Android OSAndroid OS
Android OS
 
Android Tutorial
Android TutorialAndroid Tutorial
Android Tutorial
 
01 03 - introduction to android
01  03 - introduction to android01  03 - introduction to android
01 03 - introduction to android
 

Kürzlich hochgeladen

Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionDilum Bandara
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 

Kürzlich hochgeladen (20)

Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An Introduction
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 

Case Study: Cool Clock - An Intro to Android Development

  • 1. Cool Clock Helping children to read analog clocks An introduction to developing custom 2D graphics Android applications Richard Creamer 2to32minus1 @gmail.com Links Home Page LinkedIn Profile Android Market Cool Clock Product Listing External Cool Apps Software Website External Cool Clock Product Details Page 1 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 2. What are 2D Custom Graphics Components? Below are several examples of 2D components previously developed by the author in Java/Swing: 2 Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved
  • 3. What are 2D Custom Graphics Components? Below are several examples of 2D components previously developed by author in Java/Swing: Custom keypad “Gel” button Process Editor Joystick for games, robotics Least-squares data/curve fitting Spring force graph layout of UML-like OO diagram showing classes & interrelationships Tiger Line map of states, counties and air fields Binary Search Tree compact graph layout & visualization 3 Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved
  • 4. The Cool Clock Application Learn Mode Clock Mode Modern Clock Hands can be interactively dragged. Includes dynamic “Before” and “After” circular arrows. Traditional running clock - no hand dragging, but supports optional dynamic circular arrows. A simple “modern” running clock. No numerals, minute ticks, etc. 4 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 6. Android - Getting Started • Getting started with Android development and the Eclipse plug-in: • Install the Java SE JDK and Java documentation: • www.oracle.com/technetwork/java/javase/downloads/index.html • Install Eclipse, the most efficient IDE for Android development: • www.eclipse.org • Install the Android SDK and the Android Eclipse Plug-In: • www.developer.android.com/sdk • After Java SE, Eclipse and the Android SDK are installed: • Set up your PATH environment variable to include: • AndroidInstallDirplatform-tools, AndroidInstallDirtools • JavaSeInstallDirbin • Use Eclipse to create a “Hello, Android!” application. • Android development book suggestions: • Hello, Android by Burnette ( 1934356565 ) • Android Wireless Development by Darcy and Conder ( 0321743016 ) • Professional Android 2 Application Development by Meier ( 0470565520 ) 6 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 7. Android Introduction • Android is a Linux-based platform/OS for mobile devices from Google. • Android is not the same as the Chrome OS. • Android programming: http://developer.android.com/ • High-level programming is done in Java SE. • Low-level programming ( NDK ) is done in C/C++. • Android NDK URL: http://developer.android.com/sdk/ndk/ • Android enforces aggressive resource management • Resources such as strings, menu and view layouts can be defined in XML or in code. • Google recommends defining resources in XML vs. in code. • Android programs have direct, programmatic access to: • GPS and other sensors ( compass, accelerometer, orientation ) • Multi-touch input, internal & external storage • Web, e-mail, SMS, Google Maps • Camera & multi-media playing/recording • Open GL ES 1.1 and 2.0 • Android apps must declare needed privileges and minimum Android version. • Android includes a rich set of UI widgets/components and a basic SQLite database. • Android app lifecycle diagram: • http://developer.android.com/reference/android/app/Activity.html#Lifecycle 7 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 8. Android Tools and App Categories • Useful tools: • adb.exe - useful command lines: • C:>adb logcat ( prints event log ) • C:>adb logcat -c ( clears the logcat history ) • C:>adb install Clock.apk ( installs an app on a device or emulator ) • Dalvic Debug Monitor ( located in sdkDirtools ) • Run ddms.bat to launch this useful console application • Explore Android file system, take app screenshots and much more • Android Virtual Device Manager ( AVD Manager ) • Create multiple virtual devices w/ various Android OS/HW configurations. • Enables testing on a range of HW configurations which may be unavailable • WindowAndroid SDK and AVD Manager • Sampling of Android app types: • Activity - A user interface screen • Intent - An action ( e.g., launch Help screen or send e-mail ) • Service - A long-running background task • Content Provider - Data accessed via a custom API ( e.g. Contacts ) • Widget - Mini home-screen applications ( e.g. Weather ) • Wallpaper - Static or animated backgrounds 8 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 10. Java Introduction • The Cool Clock project was coded entirely in Java SE and XML. • Java attributes: • Fast, mature, portable, object-oriented language with bright future • Extensive I/O, Concurrency, Collections, Reflection and Generics support • Source code is compiled into platform-independent byte code files • Note: Android byte codes are not compatible with standard Java byte codes • A platform-specific program called a Java Virtual Machine ( JVM ) is used to execute Java applications. Some JVMs perform JIT compilation. • On the Android platform, a special JVM, the Dalvic Virtual Machine, is used. • Large websites such as Amazon rely on/use Java extensively • Unlike C/C++, only the JVM needs to be ported and recompiled for different platforms • A basic Java program: ( note that Java now has a printf() method ) MyProgram.java public class MyProgram { public static void main( String [] args ) { System.out.printf( "%sn", "Hello, Introductory 2D Android PPT!" ); } } • Compiling a program: ( javac is the compiler ) C:> javac MyProgram.java  MyProgram.class • If a class has a main() method, it can be run from the command line: ( java.exe is the JVM ) C:> java MyProgram  Hello, Introductory 2D Android PPT! 10 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 11. Java Introduction • IDEs • Popular IDEs for Java include Eclipse and NetBeans. Both are free. • Eclipse is usually used for Android development because it has a highlyintegrated plug-in for Android. • Books • Many good introductory Java books exist, here are a few: • Learning Java by Niemeyer and Knudsen ( 0596008732 ) • Core Java, Vols. I and II by Horstmann and Cornell ( 0132354764 ) • More advanced books: • Effective Java by Bloch, 2nd Edition ( 0321356683 ) ( essential ) • Java Concurrency in Practice by Goetz ( 0321349601 ) • Concurrent Programming in Java by Lea ( 0201310090 ) • Java Threads by Oakes and Wong ( 0596007829 ) • Java Generics and Collections by Naftalin & Wadler ( 0596527756 ) • Java NIO by Hitchens ( 0596002882 ) • Java Network Programming by Harold, 3rd Edition ( 0596007218 ) 11 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 13. Components of an Android App Android App Manifest XML File • Declares minimum Android OS version • Declares app version ID • Declares configuration changes handled by app • Declares all Activities in app Auto-Generated Java Files • A Java class named “R” • R.java contains static final int IDs for all resources Main Activity Class • Handles initialization tasks in onCreate() • Sets app View component ( app content view ) • Handles menu events • Launches other Activities • Handles configuration change events ( orientation ) • Responds to Android app lifecycle state changes Resources • drawable ( bitmaps, icons ) • layout ( GUI layout/content ) • menu ( menu content ) • values ( strings, arrays ) • xml ( Preferences screen layout & content, etc. ) Other Activity Classes Main View Class • Sub-activities such as: • PrefsActivity.java ( Preferences screen ) • AboutActivity.java ( About screen ) • HelpActivity.java ( Help screen ) • Extends android.view.View class • Handles presentation ( drawing, widgets, forms ) • Handles touch events • Example: ClockPanel.java Other Utility Classes • Other classes such as: • Vector2d.java Copyright (c) Richard Creamer 2011 - All Rights Reserved 13
  • 15. Android Application Lifecycle • Android can pause or destroy applications at any time • Android sends various lifecycle state-change notifications to applications including: • onCreate() • onRestoreInstanceState() is called right after onCreate() • onStart() • onPause() • onSaveInstanceState() is called right before onPause() • onResume() • onStop() • onDestroy() • Applications should override and respond appropriately to most of these notifications to preserve state and to minimize resource and CPU use when not actively running Free resources and persist state Fetch prior state, instantiate app elements, call setContentView() App Launch Pause worker threads & persist state onStop() onPause() Another Activity to foreground, e.g. onDestroy() Process killed onCreate() Activity invisible Resume worker threads onStart() Event Event onResume() Activity is visible 15 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 16. Actual logcat Trace of Cool Clock State Notifications Stopping worker threads is one example of how apps should respond to lifecycle notifications Start Cool Clock I/ActivityManager( 69): Start proc net.maxuint32.coolapps for activity net.maxuint32.coolapps/.CoolClock: pid=279 uid=10041 gids={} I/System.out( 279): >>>>>>>>>>>> CoolClock.onCreate() <<<<<<<<<<< I/System.out( 279): >>>>>>>>>>>> ClockPanel Constructor <<<<<<<<<<< ( called by CoolClock ) I/System.out( 279): >>>>>>>>>>>> CoolClock.onStart() <<<<<<<<<<< I/System.out( 279): >>>>>>>>>>>> CoolClock.onResume() <<<<<<<<<<< I/System.out( 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<< ( called by CoolClock ) I/System.out( 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<< ( called by CoolClock ) I/ActivityManager( 69): Displayed activity net.maxuint32.coolapps/.CoolClock: 4724 ms (total 4724 ms) I/System.out( 279): ClockRunThread threadState: Running Initially, clock run thread is active I/System.out( 279): ClockRunThread threadState: Running Press Phone Menu Button I/System.out( 279): >>>>>>>>>>>> CoolClock.onCreateOptionsMenu() <<<<<<<<<<< I/System.out( 279): ClockRunThread threadState: Running I/System.out( 279): ClockRunThread threadState: Running Press Settings Button I/ActivityManager( 69): Starting activity: Intent { cmp=net.maxuint32.coolapps/.PrefsActivity } I/System.out( 279): >>>>>>>>>>>> CoolClock.onOptionsMenuClosed() <<<<<<<<<<< I/System.out( 279): >>>>>>>>>>>> CoolClock.onPause() <<<<<<<<<<< I/System.out( 279): >>>>>>>>>>>> ClockPanel.pause() <<<<<<<<<<< ( called by CoolClock ) I/ActivityManager( 69): Displayed activity net.maxuint32.coolapps/.PrefsActivity: 1970 ms (total) I/System.out( 279): ClockRunThread threadState: Suspended I/System.out( 279): ClockRunThread threadState: Suspended Exit Preferences Screen I/System.out( I/System.out( I/System.out( I/System.out( I/System.out( 279): >>>>>>>>>>>> ClockPanel.notifyClockPrefsChanged() <<<<<<<<<<< 279): >>>>>>>>>>>> CoolClock.onResume() <<<<<<<<<<< 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<< ( called by CoolClock ) 279): ClockRunThread threadState: Running 279): ClockRunThread threadState: Running Press Back Button to show Home Screen I/System.out( I/System.out( I/System.out( I/System.out( I/System.out( 279): >>>>>>>>>>>> CoolClock.onPause() <<<<<<<<<<< 279): >>>>>>>>>>>> ClockPanel.pause() <<<<<<<<<<< 279): >>>>>>>>>>>> CoolClock.onStop() <<<<<<<<<<< 279): >>>>>>>>>>>> ClockPanel.stop() <<<<<<<<<<< 279): >>>>>>>>>>>> CoolClock.onDestroy() <<<<<<<<<<< Copyright (c) Richard Creamer 2011 - All Rights Reserved When Main Menu is displayed, clock run thread remains active After any onPause() event, clock run thread is suspended and is not consuming CPU time for drawing After PrefsActivity has exited, clock is again visible and run thread state is active 16
  • 17. How Cool Clock Responds to State Changes Android Event Stream App Launch • Android starts CoolClock Activity • CoolClock.onCreate() • CoolClock.onStart() • CoolClock.onResume() • Clock now Running and visible Press phone menu button • CoolClock.onCreateOptionsMenu() • Clock run thread still Running Press Settings button on Main Menu • Android starts PrefsActivity • CoolClock.onOptionsMenuClosed() • CoolClock.onPause() • PrefsAcvitivity is made visible • Clock is now behind Settings screen • Clock run thread is Suspended • Instantiates ClockPanel • Calls Activity.setContentView( clockPanel ) User exits PrefsActivity User presses phone Back button • ClockPanel .onActivityResult () •ClockPanel.notifyClockPrefsChange() • CoolClock.onResume() • Clock now Running and visible again • CoolClock.onPause() • CoolClock.onStop() • CoolClock.onDestroy() • CoolClock now destroyed • Calls ClockPanel.resume(): • Sets clock run thread state to Running • Calls ClockPanel.pause(): • Sets clock run thread state to Suspended • Calls ClockPanel.notifyClockPrefsChange(): • Reloads user preference values from Android • Redraws clock with updated preferences • Calls ClockPanel.resume(): • Sets clock run thread state to Running • Calls ClockPanel.pause(): • Sets clock run thread state to Suspended • Calls ClockPanel.stop(): • Stops clock run thread 17 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 18. How to reference Android resources 18
  • 19. How Android resources are referenced 1 From Java 2 From XML Recall resource types include 1 Resources • drawable ( bitmaps, icons ) • layout ( Activities ) • menu ( menu content ) • values ( strings, arrays ) • xml ( Preference screen layout & content, etc. ) Excerpt from CoolClock.java public boolean onCreateOptionsMenu( Menu menu ) { super.onCreateOptionsMenu( menu ); MenuInflater inflater = getMenuInflater(); inflater.inflate( R.menu.menu, menu ); return true; } Recall that R is an auto-generated Java class 2 From Java Clockresmenumenu.xml From XML Excerpt from Clockresxmlsettings.xml <CheckBoxPreference android:key="hide_circular_arrows" android:title="@string/hide_circular_arrows_title" android:summary="@string/hide_circular_arrows_summary" android:defaultValue="true" /> Clockresvaluesstrings.xml Excerpt <string name="hide_circular_arrows_title">Hide Circular Arrows</string> 19 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 21. Creating Main Menus CoolClock.java: 2 Handle Main Menu Creation 1 Define Main Menu GUI @Override public boolean onCreateOptionsMenu( Menu menu ) { super.onCreateOptionsMenu( menu ); // Call base class first MenuInflater inflater = getMenuInflater(); inflater.inflate( R.menu.menu, menu ); return true; } CoolClock.java: 3 Handle Main Menu Button Presses Clockresmenumenu.xml <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/settings" android:title="@string/settings_label" android:alphabeticShortcut="@string/settings_shortcut" /> <item android:id="@+id/help" android:title="@string/help_option" android:alphabeticShortcut="@string/help_shortcut" /> <item android:id="@+id/about" android:title="@string/about_label" android:alphabeticShortcut="@string/about_shortcut" /> <item android:id="@+id/exit" android:title="@string/exit_label" android:alphabeticShortcut="@string/exit_shortcut" /> </menu> @Override public boolean onOptionsItemSelected( MenuItem mi ) { super.onOptionsItemSelected( mi ); // Called first switch ( mi.getItemId() ) { case R.id.help: startActivity( new Intent( this, HelpActivity.class ) ); return true; case R.id.settings: startActivityForResult( new Intent( this, PrefsActiviy.class ), PREFS_ACTIVITY_ID ); return true; case R.id.about: startActivity( new Intent( this, AboutActivity.class ) ); return true; About Menu “Inflation” case R.id.exit: In this line of code: finish(); inflater.inflate( R.menu.menu, menu ); return true; The MenuInflater reads menu.xml and then } programmatically adds the MenuItems to return false; the method argument: menu } Copyright (c) Richard Creamer 2011 - All Rights Reserved Result: Main Menu Screenshot 21
  • 22. Introduction to Android Preferences 22
  • 23. Android Preferences Overview • Android includes an easy-to-use Preferences sub-system that allows end users to customize an application’s settings. • Preferences are roughly equivalent to ToolsOptions for desktop applications. Pressing the Settings button displays the Preferences/Settings menu The main menu is displayed when the user presses the device’s Menu button The Preferences screen GUI layout/content is defined in settings.xml ( see slide 25 ) 23 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 24. Android Preferences • The Android Preferences sub-system provides: • XML-based declarative GUI layout definition of Preferences menus/screens • Automatic launching of Preferences screens and their lifecycle management • Transparent persistence of user changes • Easy-to-use API for retrieving persisted Preferences parameter values • The Cool Clock Preferences screens: Primary Preferences Screen Clock Mode Options Minute Display Mode Options Hand Linked to Circular Arrows Options 24 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 25. settings.xml ( Defines Preferences GUI layout and content* ) <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="hide_sec_hand" android:title="@string/hide_sec_hand_title" android:summary="@string/hide_sec_hand_summary" android:defaultValue="true" /> <CheckBoxPreference android:key="hide_circular_arrows" android:title="@string/hide_circular_arrows_title" android:summary="@string/hide_circular_arrows_summary" android:defaultValue="true" /> <CheckBoxPreference android:key="hide_digital_time" android:title="@string/hide_digital_time" android:summary="@string/hide_digital_time_summary" android:defaultValue="true" /> <CheckBoxPreference android:key="hide_phrase_time" android:title="@string/hide_phrase_time" android:summary="@string/hide_phrase_time_summary" android:defaultValue="true" /> <ListPreference android:key="clock_modes" android:title="@string/clock_mode_label" android:summary="@string/clock_mode_summary" android:defaultValue="@string/clock_mode_label" android:entries="@array/clock_modes" android:entryValues="@array/clock_mode_values" /> <ListPreference android:key="minute_display_modes" android:title="@string/minute_display_mode_title" android:summary="@string/minute_display_mode_summary" android:defaultValue="@string/every_five_minutes" android:entries="@array/minute_display_modes" android:entryValues="@array/minute_display_mode_values" /> <ListPreference android:key="arc_modes" android:title="@string/arc_mode_label" android:summary="@string/arc_mode_summary" android:defaultValue="@string/arc_mode_minutes" android:entries="@array/arc_modes" android:entryValues="@array/arc_modes_values" /> </PreferenceScreen> *Multiple-choice widget values are stored in resvaluesarrays.xml 25 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 26. How Preferences Menus Work - Details User Presses In main activity ( CoolClock.java ) public boolean onCreateOptionsMenu( Menu menu ) { super.onCreateOptionsMenu( menu ); MenuInflater inflater = getMenuInflater(); inflater.inflate( R.menu.menu, menu ); return true; } Calls (1st time) Android ( R.menu.menu ) Phone Menu button Displays Android Main menu Presses User Settings button Calls Android Time Displays Android User The “inflater” reads the defined main menu items defined in menu.xml and programmatically adds these menu items to the method argument’s Menu object Preferences screen Interacts with User Android Closes In main activity ( CoolClock.java ) public boolean onOptionsItemSelected( MenuItem mi ) { switch ( mi.getItemId() ) { ... cases for other main menu items ... case R.id.settings: startActivityForResult( new Intent( this, PrefsActivity.class ), PREFS_ACTIVITY_ID ); return true; ... Tells Android to launch } Preferences screen return false; } Preferences screen Preferences screen Calls In main activity ( CoolClock.java ) protected void onActivityResult( int requestCode, int resultCode, Intent data ) { if ( requestCode == PREFS_ACTIVITY_ID ) { Reads updated Preferences final ClockState newCs = PrefsActivity.getStoredPrefs( this ); clockPanel.notifyClockPrefsChanged( newCs ); Notifies ClockPanel of user } Preferences state changes } 26 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 27. Retrieving Preferences Values • Stored Preferences values are read using { ParamKey, DefaultValue } pairs. • Example of how to retrieve Android-persisted Preferences values: PrefsActivity.java ( excerpts ) public final class PrefsActivity extends PreferenceActivity { public static final boolean HIDE_SECOND_HAND_DEFAULT = true; Default parameter value // Key values for accessing parameters from standard Android Preferences/Settings sub-system private static final String HIDE_SEC_HAND_KEY = "hide_sec_hand"; Parameter key private static final String ARC_MODES_KEY = "arc_modes"; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); addPreferencesFromResource( R.xml.settings ); } Reference to settings.xml ...retrieving Preferences parameter values... // Get a SharedPreferences object SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( cc ); { ParamKey, DefaultValue } pair // Grab actual Preferences/Settings VALUES boolean hideSecondHand = sp.getBoolean( HIDE_SEC_HAND_KEY, HIDE_SECOND_HAND_DEFAULT ); String arcModeString = sp.getString( ARC_MODES_KEY, defaultArcModeMenuString ); } 27 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 28. Creating Simple TextView Based Activities 28
  • 29. About Panel Creating a Simple TextView Activity 1 Define GUI layout about.xml <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dip"> <TextView android:id="@+id/about_content" About panel’s android:layout_width="wrap_content" text content android:layout_height="wrap_content" android:text="@string/about_text" /> </ScrollView> Note nesting of GUI components android.widget.TextView TextView extends android.view.View 2 Define activity class AboutActivity.java package net.maxuint32.coolapps; import android.app.Activity; import android.os.Bundle; Activity’s GUI layout public class AboutActivity extends Activity { @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.about ); } } strings.xml (excerpt) <string name="about_text"> CoolClock v0.8n Copyright (c) Richard Creamer 2011n All Rights Reservedn Email: maxuint32.coolapps@gmail.comnn CoolClock can run in three modes:nn 1. Learn Mode:nn This mode helps teach children how to read an analog clock.nn Children can drag clock hands and watch:nn - The other hands moven - How the digital time changesn - How the "phrase" time changesn - "Before" and "after" circular arrowsnn 2. Clock Mode:nn In this mode, CoolClock is just a normal analog clock - it displays the current time and runs.nn 3. Modern Clock:nn In this mode, a more modern clock is drawn, without numbers or minute ticks. </string> 3 Start activity CoolClock.java (excerpt) @Override public boolean onOptionsItemSelected( MenuItem mi ) { ... switch ( mi.getItemId() ) { case R.id.about: startActivity( new Intent( this, AboutActivity.class ) ); return true; } .... Here we are starting the return false; } AboutActivity when a main 4 Define text content Copyright (c) Richard Creamer 2011 - All Rights Reserved menu item is pressed, but Activities can be started at any time. 29
  • 30. Custom 2D Graphical Components 30
  • 31. A Basic Custom 2D Graphics App/Component • GraphicsTest1 is a very simple custom-drawn 2D component • It draws a gradient background with a random number of spheres of random color and opacity • It ignores many best practices such as avoiding expensive computations in the GUI thread 31 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 32. GraphicsTest1 - Salient Java Source Code Elements package ... import ... Steps for creating a custom 2D component Activity 1. 2. 3. 4. public class GraphicsTest1 extends Activity { ... attributes... @Override public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); requestWindowFeature( Window.FEATURE_NO_TITLE ); setContentView( new GraphicsView( this ) ); } 4 Create new class which extends android.view.View New class must override onDraw() onDraw() does all drawing Call Activity.setContentView( custom 2D component ) GraphicsTest1 sets its content view to an instance of GraphicsView // Nested class that fills its bounds with random spheres private static class GraphicsView extends View { 1 ... attributes... public GraphicsView( Context context ) { super( context ); } 2 GraphicsView overrides onDraw() @Override protected void onDraw( Canvas canvas ) { // Do all drawing Do all drawing: } } // End nested GraphicsView class background + spheres/graphics 3 Note: The GraphicsView class does not have to be a nested class - it was only done this way so the entire Activity + View would fit into a single Java file... } // End GraphicsTest1 class 32 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 33. GraphicsTest1 - Complete Java Source Code package org.example.graphics; import java.util.Random; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.Window; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.RadialGradient; import android.graphics.Shader; public class GraphicsTest1 extends Activity { 3 Do all drawing: gradient paint background + spheres GraphicsTest1 sets its content view to be an instance of GraphicsView @Override public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); requestWindowFeature( Window.FEATURE_NO_TITLE ); setContentView( new GraphicsView( this ) ); } 4 private void drawRandSpheresAndBkg( Canvas canvas ) { int numCircles = rand.nextInt( maxCircles ); if ( numCircles < minCircles ) numCircles = minCircles; int w = this.getWidth(); int h = this.getHeight(); Paint bkgPaint = new Paint(); bkgPaint.setShader( new LinearGradient( 0, 0, w, h, c1, c2, Shader.TileMode.MIRROR ) ); canvas.drawPaint( bkgPaint ); // This fills Canvas with background gradient paint for ( int i = 0; i < numCircles; ++i ) { int xc = rand.nextInt( w ); Such methods can be computationally int yc = rand.nextInt( h ); expensive and should not be done in the int r = rand.nextInt( ( w + h )/16 ); main GUI thread! Run expensive tasks int red = rand.nextInt( 255 ); in another thread! Then, post results int green = rand.nextInt( 255 ); back to GUI thread using a Handler. int blue = rand.nextInt( 255 ); int alpha = rand.nextInt( 255 ); int color = Color.argb( alpha, red, green, blue ); drawSphereImage( canvas, xc, yc, r, color ); // Draw the sphere } } public void drawSphereImage( Canvas canvas, float xc, float yc, float radius, int color ) { if ( radius < 3.0f ) radius = 3.0f; // Nested class that fills its bounds with random spheres Paint paint = new Paint( Paint.ANTI_ALIAS_FLAG ); Class should final float highlightOffsetInPct = 0.4f; 1 private static class GraphicsView extends View { extend View float highlightOffset = highlightOffsetInPct * radius; float highlightXc = xc - highlightOffset; private final int maxCircles = 150, minCircles = 5; float highlightYc = yc - highlightOffset; private final int c1 = 0xff4466aa, c2 = 0xff112244; final float radiusScaleFactor = 3; private final Random rand = new Random( System.currentTimeMillis() ); float highlightRadius = radiusScaleFactor * highlightOffset; RadialGradient rg = new RadialGradient( highlightXc, highlightYc, public GraphicsView( Context context ) { highlightRadius, 0xffffffff, color, Shader.TileMode.MIRROR ); super( context ); GraphicsView paint.setShader( rg ); } overrides canvas.drawCircle( xc, yc, radius, paint ); onDraw() 2 } @Override } // End GraphicsView class protected void onDraw( Canvas canvas ) { } // End GraphicsTest1 class drawRandSpheresAndBkg( canvas ); } 33 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 34. Custom 2D View Components - Overview Creating the basic GraphicsTest1 class was pretty simple: • Create a class that extends android.view.View ( GraphicsView ) • GraphicsView must override View.onDraw( Canvas canvas ) • In onDraw(), perform all the necessary drawing ( images, shapes, primitives ) • Main Activity calls setContentView() with an instance of GraphicsView When creating non-trivial components, other details become important, including: • Never allow direct access to a GUI’s state from a non-GUI thread ( thread-safe design ) • Appropriately respond to lifecycle events • Use background/worker threads to perform long-running, computationally-expensive tasks • Methods to return results from non-GUI threads to the main GUI thread include: • Call Activity.runOnUiThread( Runnable ) • Call the android.os.Handler.post( Runnable ) method of a Handler object • The Handler must be instantiated in the GUI thread • If user touch input is required the View-derived class should: • Implement the OnTouchListener interface • Implement View.onTouch() • Handle all touch events and object hit testing if applicable • Minimize use of system resources • Call base class methods at the appropriate point when overriding methods 34 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 36. Implement OnTouchListener.onTouch() public boolean onTouch( View v, MotionEvent me ) { if ( curPrefs.clockOperationMode != E.ClockOperationMode.LearnMode ) return false; if ( !curPrefs.enableHandDragging || me.getPointerCount() > 1 ) // Only handle 1 finger at this time return false; float x = me.getX(); float y = me.getY(); int action = me.getAction() & MotionEvent.ACTION_MASK; ClockPanel implements the OnTouchListener interface in order to receive touch input switch ( action ) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: E.ClockHands hand = isCloseToHand( x, y ); if ( hand == E.ClockHands.None ) return false; inDrag = true; dragHand = hand; return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: inDrag = false; dragHand = E.ClockHands.None; return true; case MotionEvent.ACTION_MOVE: processMove( x, y ); return true; Begin a hand drag... Terminate a hand drag... Move the current drag hand... } return false; } 36 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 37. Graphics techniques used in Cool Clock 37
  • 38. Cool Clock’s Graphics Layers Background LinearGradient shader mirroring = true Fill clock oval Fill clock oval, clipped, with LinearGradient shader Add hour and minute numbers + minute ticks ( This is what is cached ) • Hands (LinearGradient shader) • Hands are drawn via Path objects • Center dot (RadialGradient shader) Upper-left highlight (LinearGradient) Lower-right highlight (LinearGradient) Digital time, Phrase time • Circular arrows fill/edges • Circular arrow labels Copyright (c) Richard Creamer 2011 - All Rights Reserved 38
  • 39. Utility Class: Vector2d (selected methods) //Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved Complete Vector2d public interface: package net.maxuint32.coolapps; import android.graphics.PointF; public float x, y; public final class Vector2d { public Vector2d(); public float x, y; public Vector2d( final float x, final float y ); public Vector2d( final float x, final float y ) { public Vector2d( final Vector2d v ); this.x = x; public Vector2d( final PointF p1, final PointF p2 ); this.y = y; public Vector2d setComponents( final float x,final float y ); } public float length(); public synchronized Vector2d rotateClockwise( final float angle ) { public float dotProduct( final Vector2d v ); rotateCounterClockwise( -angle ); public float cosAngle( final Vector2d v ); return this; public Vector2d zero(); } public Vector2d add( final Vector2d v ); public synchronized Vector2d rotateCounterClockwise( final float angle ) { float cos = ( float ) Math.cos( angle ); public Vector2d mult( final float factor ); float sin = ( float ) Math.sin( angle ); public Vector2d div( final float quotient ); CG Rotation Matrix float newX = cos * x + sin * y; public Vector2d rotateClockwise( final float angle ); float newY = -sin * x + cos * y; x’ x public Vector2d rotateCounterClockwise( final float angle ); cos ѳ sin ѳ x = newX; public Vector2d setLength( final float length ); y’ -sin ѳ cos ѳ y y = newY; public Vector2d normalize(); return this; public Vector2d parallelOfLength( final float length ); } public synchronized Vector2d setLength( final float length ) { normalize(); x = x * length; y = y * length; return this; } public synchronized Vector2d normalize() { float length = length(); if ( length == 0f ) throw new IllegalStateException( "Vector length is zero! Cannot divide by zero!" ); x = x / length; y = y / length; return this; } public synchronized float length() { return ( float ) Math.sqrt( x * x + y * y ); } 39 } Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved =
  • 40. Drawing Shapes Using Paths 1. 2. 3. 4. 5. 6. Create a Path object p ( see below for details ) Call p.moveTo() for starting point 0 Call p.lineTo() or p.arcTo() points 1 - 9 in clockwise order Call p.close() Configure canvas paint or shader ( can be fill or stroke mode ) Call canvas.drawPath( p ) 0 1 2 3 4 8 7 9 private Path getRhsArrow( float a1, float a2 ) { // Almost complete & accurate... Yellow arrows are Vector2d float r1 = Innermost arrowhead side point radius 6 rotation angles float r2 = Annulus inner radius 5 float r3 = Annulus center radius float r4 = Annulus outer radius float r5 = Outermost arrowhead side point radius float deltaAInDeg = toDegrees( F.abs( a1 - a2 ) ); ( xc, yc ) Use Vector2d class Path p = new Path(); to handle math Vector2d v = new Vector2d( 0, -r3 ); ( previous slide ) p.moveTo( xc + v.x, yc + v.y ); v.rotateClockwise( arrowheadAngle ).setLength( r5 ); p.lineTo( xc + v.x, yc + v.y );  method = Path methods v.setLength( r4 );  method = Vector2d methods p.lineTo( xc + v.x, yc + v.y ); p.arcTo( new RectF( xc - r4, yc - r4, xc + r4, yc + r4 ), -90 + arrowheadAngle, deltaAInDeg - 2f * arrowheadAngle ); p.lineTo( xc + v.x, yc + v.y ); v.rotateClockwise (arrowheadAngle ).setLength( r3 ); // We can chain method calls because most Vector2d methods return this p.lineTo( xc + v.x, yc + v.y ); v.rotateCounterClockwise( arrowheadAngle ).setLength( r1 ); p.lineTo( xc + v.x, yc + v.y ); v.setLength( r2 ); p.lineTo( xc + v.x, yc + v.y ); p.arcTo( new RectF( xc - r2, yc - r2, xc + r2, yc + r2 ), -90 + deltaAInDeg - arrowheadAngle, -( deltaAInDeg - 2f * arrowheadAngle ) ); v.rotateCounterClockwise( F.abs( a1 - a2 ) - 2 * arrowheadAngle ).setLength( r1 ); p.lineTo( xc + v.x, yc + v.y ); p.close(); return p; 40 } Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 42. The FontMetrics Class and Paint.getTextBounds() • Android provides the android.graphics.Paint.FontMetrics class: • Provides Typeface dimensions useful for correctly positioning text • FontMetrics class attributes: • ascent - “Recommended” distance above baseline • bottom - Maximum distance below the base line for lowest glyph • descent - “Recommended” distance below baseline • leading - “Recommended” space between lines of text • top - Maximum distance above baseline for tallest glyph • Important: • Attribute values pertaining to distance above the baseline are NEGATIVE Most Useful Attributes (-) top baseline Glyph ( bottom - top ) bottom • Note: Paint.getTextBounds() often provides sufficient information ( FontMetrics object not always needed - see following examples... ) 42 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 43. Drawing Text - Code General-purpose text drawing method supporting horizontal and vertical alignment public enum TextVertAlign { Top, Middle, Baseline, Bottom }; // Enumeration representing vertical alignment positions public static void drawHvAlignedText( Canvas canvas, float x, float y, String s, Paint p, Paint.Align horizAlign, TextVertAlign vertAlign ) { // Set horizontal alignment p.setTextAlign( horizAlign ); // Get bounding rectangle which we’ll need below... Rect r = new Rect(); p.getTextBounds( s, 0, s.length(), r ); // Note: r.top will be negative // Compute y-coordinate we'll need for drawing text for specified vertical alignment float textX = x; float textY = y; switch ( vertAlign ) { Also, look at: case Top: • Paint.measureText() textY = y - r.top; // Recall that r.top is negative • Paint.getFontMetrics() break; • android.text.StaticLayout case Middle: • android.widget.FrameLayout textY = y - r.top - r.height()/2; break; case Baseline: // Default behavior - no changes to y-coordinate break; case Bottom: textY = y - ( r.height() + r.top ); break; } canvas.drawText( s, textX, textY, p ); // Now we can draw the text with the proper ( x, y ) coordinates } 43 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 44. Text Drawing Example // Excerpts from TextDrawing demo... public enum TextVertAlign { Top, Middle, Baseline, Bottom }; private float fontScaleFactor = 0.08f; // Crude scaling to display private float crosshairScaleFactor = 0.05f; // Crude scaling to display private int numTextColumns = Paint.Align.values().length; private int numTextRows = TextVertAlign.values().length; private float crosshairSize = 10f; @Override protected void onDraw( Canvas canvas ) { canvas.drawPaint( bkgPaint ); // Clear background // Compute some variables we'll need float w = getWidth(); float h = getHeight(); crosshairSize = ( w < h ) ? w * crosshairScaleFactor : h * crosshairScaleFactor; float textSize = ( w < h ) ? w * fontScaleFactor : h * fontScaleFactor; textPaint.setTextSize( textSize ); float colWidth = ( w - 4 * crosshairSize ) / ( numTextColumns - 1 ); float lineSep = h / ( numTextRows + 1f ); float x = 2f * crosshairSize; // Loop over horizontal and vertical alignment enum values // Draw string using loop values for horiz and vert alignment for ( Paint.Align ha : Paint.Align.values() ) { float y = lineSep; for ( TextVertAlign va : TextVertAlign.values() ) { drawHvAlignedText( canvas, x, y, s, textPaint, ha, va ); drawCrosshairs( canvas, x, y ); y += lineSep; } x += colWidth; } Left Center Right Top Middle Baseline Bottom } Copyright (c) Richard Creamer 2011 - All Rights Reserved 44
  • 46. Optimization • Because onDraw() is called frequently and because much of the Cool Clock graphics drawing is repetitive and computationally expensive, two techniques were used to reduce CPU usage and to maintain a responsive GUI: • Cache the screen bitmap ( background + clock face + ticks + numbers ) • The clock face only changes if: • The user modifies certain Preferences settings • The device orientation changes • As a result, the background and clock face are computed once into cached bitmaps, one for each orientation, until Preferences changes require updates. • Each time onDraw() is called, a cached bitmap is efficiently copied to the screen, then only the hands, circular arrows, highlights, and time text fields are drawn. • Compute the screen bitmap in a background thread: • To keep the GUI responsive, the long-running task of drawing the background and clock face are performed in a background thread - not in the GUI thread. • While a bitmap is not ready, onDraw() simply fills the screen with the background paint. • When the bitmap computation is completed and the background worker thread terminates, a new Runnable is posted to the GUI Handler which adopts the new Bitmap and then calls invalidate(). • invalidate() queues up a future call to onDraw() 46 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 47. Skeleton Definition of Background Worker Thread private static class ClockFaceRendererTask extends Thread { // Fields containing final defensive copies of a snapshot of ClockPanel fields required for rendering clock face bitmap ... private final Bitmap bm; // The output of this worker thread public ClockFaceRendererTask( final ClockPanel clockPanel ) { // Make final defensive copies of ClockPanel state data bm = rp.bm.getAndSet( null ); // Use AtomicReference<Bitmap> to atomically assume exclusive ownership of Bitmap } public void run() { // Validate remaining input data renderClockFace(); // Thread terminates when run() completes } public Bitmap getFinalBitmap() { // Ensure renderingCompleted == true return bm; } private void renderClockFace() { // Draw background, clock face // Draw numbers, ticks renderingCompleted = true; } private void drawNumbers( Canvas canvas, float xc, float yc, float radius ) { ... } private void drawTicks( Canvas canvas, float xc, float yc, float radius ) { ... } } 47 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 48. Invoking the ClockFaceRendererTask • In order to create and run a clock renderer task in a background thread, we need to create an outer “monitor” thread to: • Instantiate and start a renderer task • Wait for the renderer sub-task to complete • After the renderer has finished computing a new bitmap, we need to define and post a Runnable task to the GUI handler which will run in the GUI thread and safely adopt the new Bitmap “later” • This is a bit complex for those new to threads ( consult the books listed on slide 11 for more details ) • Below is a diagram of what the next page’s callRenderer() method is doing... Non-GUI Worker Thread 1 callRenderer() Defines complex thread Instantiates ClockFaceRendererTask Starts the ClockFaceRendererTask Waits for the ClockFaceRendererTask to complete/terminate Defines a Runnable which must be run in the GUI thread which adopts the newly computed Bitmap 2 callRenderer() Starts outer thread and returns immediately Posts the above Bitmap adoption Runnable to the GUI Handler which will run “later” 48 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 49. Invoking the ClockFaceRendererTask A worker thread launched and managed from within an outer monitor thread private void callRenderer() { // Called only in onDraw() - returns immediately after defining and starting background worker thread final ClockPanel cp = this; Thread t = new Thread( new Runnable() { Define an outer thread to run in background which, in turn, starts and monitors a nested ClockFaceRendererTask thread 1 public void run() { 2 clockFaceRenderer = new ClockFaceRendererTask( cp ); // ClockFaceRendererTask extends Thread clockFaceRenderer.start(); // Start background renderer task asynchronously ( not in GUI thread ) // Wait for the renderer thread to terminate while ( true ) { try { clockFaceRenderer.join(); // join() waits for thread to terminate break; } catch ( InterruptedException ie ) { } } Instantiate and start renderer task thread Outer thread waits for renderer thread to complete 3 // New bitmap now computed - now have GUI thread adopt new bitmap reference and redraw itself final Bitmap newBitmap = clockFaceRenderer.getFinalBitmap(); if ( newBitmap == null ) throw new IllegalStateException( "Null bitmap returned from ClockFaceRendererTask!" ); guiThreadHandler.post( new Runnable() { public void run() { RenderParams rp = getCurRp(); Outer thread posts a sub-task to GUI Handler to rp.bm.set( newBitmap ); 4 adopt newly computed bitmap which will run computingBitmap = false; “at a later time” in the GUI thread invalidate(); } } ); } } ); // End outer thread definition // Start the thread we just defined - then return immediately t.start(); } After defining the outer thread, we start it and return immediately Copyright (c) Richard Creamer 2011 - All Rights Reserved 5 49
  • 51. Builder Pattern Use • The ClockPanel attributes can be initialized in many different ways depending on use context. • As a result, providing a Builder pattern to construct ClockPanel objects was implemented. • The main CoolClock Activity creates its ClockPanel component using this code: ClockState cs = PrefsActivity.getStoredPrefs( this ); // Get settings from previous Cool Clock execution clockPanel = new ClockPanel.ClockPanelBuilder(). // Construct a Builder object context( this ). // Set desired field values hideSecondHand( cs.hideSecondHand ). Note “dot” hideArcs( cs.hideCircularArrows ). operator use hideDigitalTime( cs.hideDigitalTime ). Reinstate the previous hideTextTime( cs.hidePhraseTime ). field values, then call clockOperationMode( cs.clockOperationMode ). buildClockPanel() to minuteNumDisplayMode( cs.minuteNumDisplayMode ). construct ClockPanel arcMode( cs.arcMode ). instance. buildClockPanel(); // Ask Builder to construct a ClockPanel object Calls private ClockPanel constructor 51 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 52. The ClockPanel Constructor The only ClockPanel constructor accepts a ClockPanelBuilder argument: private ClockPanel( ClockPanelBuilder cpb ) { super( cpb.context ); // Call base class this.curPrefs = cpb.cs; // Data class encapsulating Preferences settings rpLand.orientation = E.Orientation.Landscape; // Data class encapsulating drawing geometry params rpPort.orientation = E.Orientation.Portrait; // Separate RenderParam object for both orientations guiThreadHandler = new Handler(); // Create Handler in the GUI thread curPrefs.enableHandDragging = ( curPrefs.clockOperationMode == E.ClockOperationMode.LearnMode ); setOnTouchListener( this ); // Subscribe to touch events } 52 Copyright (c) Richard Creamer 2011 - All Rights Reserved
  • 53. Builder Pattern Implementation public static class ClockPanelBuilder { // Nested static class in ClockPanel.java public ClockPanelBuilder hidePhraseTime( boolean hidePhraseTime ) { cs.hidePhraseTime = hidePhraseTime; return this; } // Reference to CoolClock Activity ( needed by ClockPanel ) private Context context; // Start with default clock param values public ClockPrefs cs = ClockPrefs.defaultClockPrefs(); public ClockPanelBuilder hideCircularArrows( boolean hideCircularArrows ) { cs.hideCircularArrows = hideCircularArrows; return this; } public ClockPanelBuilder() { } public ClockPanelBuilder context( Context context ) { this.context = context; return this; } public ClockPanelBuilder minuteNumDisplayMode( E.MinuteNumDisplayMode minuteNumDisplayMode ) { cs.minuteNumDisplayMode = minuteNumDisplayMode; return this; } public ClockPanelBuilder hideHourHand( boolean hideHourHand ) { cs.hideHourHand = hideHourHand; return this; } public ClockPanelBuilder arcMode( E.ArcMode arcMode ) { cs.arcMode = arcMode; return this; } public ClockPanelBuilder hideMinuteHand( boolean hideMinuteHand ) { cs.hideMinuteHand = hideMinuteHand; return this; } public ClockPanelBuilder digitalTimeMode( E.DigitalTimeMode digitalTimeMode ) { cs.digitalTimeMode = digitalTimeMode; return this; } public ClockPanelBuilder hideSecondHand( boolean hideSecondHand ) { cs.hideSecondHand = hideSecondHand; return this; } public ClockPanelBuilder clockOperationMode( E.ClockOperationMode clockOperationMode) { cs.clockOperationMode = clockOperationMode; return this; } public ClockPanelBuilder enableHandDragging( boolean enableHandDragging ) { cs.enableHandDragging = enableHandDragging; return this; } public ClockPanel buildClockPanel() { if ( context == null ) throw new IllegalStateException( "Context field must be non-null before calling buildClockPanel()!" ); return new ClockPanel( this ); // Call private constructor } public ClockPanelBuilder hideDigitalTime( boolean hideDigitalTime ) { cs.hideDigitalTime = hideDigitalTime; return this; } } Copyright (c) Richard Creamer 2011 - All Rights Reserved 53
  • 54. Cool Clock Helping children to read analog clocks An introduction to developing custom 2D graphics Android applications Richard Creamer 2to32minus1 @gmail.com Links Home Page LinkedIn Profile Android Market Cool Clock Product Listing External Cool Apps Software Website External Cool Clock Product Details Page 54 Copyright (c) Richard Creamer 2011 - All Rights Reserved