54
Cool Clock Helping children to read analog clocks An introduction to developing custom 2D graphics Android applications Richard Creamer 2to32minus1 -at- gmail -dot- com http://sites.google.com/site/rickcreamer http://www.linkedin.com/in/rickcreamer http://sites.google.com/site/coolappssoftware 1 Copyright (c) Richard Creamer 2011 - All Rights Reserved

Cool Clock Android Dev Intro

  • Upload
    surnj1

  • View
    50

  • Download
    2

Embed Size (px)

DESCRIPTION

A cool application in Android.

Citation preview

Page 1: Cool Clock Android Dev Intro

Cool ClockHelping children to read analog clocks

An introduction to developing custom 2D graphics Android applications

Richard Creamer

2to32minus1 -at- gmail -dot- comhttp://sites.google.com/site/rickcreamerhttp://www.linkedin.com/in/rickcreamer

http://sites.google.com/site/coolappssoftware

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

Page 2: Cool Clock Android Dev Intro

2

What are 2D Custom Graphics Components?

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

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

Page 3: Cool Clock Android Dev Intro

The Cool Clock Application

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

Learn Mode

Hands can be interactively dragged. Includes dynamic

“Before” and “After” circular arrows.

Clock Mode

Traditional running clock - no hand dragging, but supports

optional dynamic circular arrows.

Modern Clock

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

ticks, etc.

Page 4: Cool Clock Android Dev Intro

Demos...Legacy Java Swing Clock App

Cool Clock App

4

Page 5: Cool Clock Android Dev Intro

Android Intro...

5

Page 6: Cool Clock Android Dev Intro

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:

• AndroidInstallDir\platform-tools, AndroidInstallDir\tools• JavaSeInstallDir\bin

• 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 )

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

Page 7: Cool Clock Android Dev Intro

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 that 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#Lifecycle7

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Page 8: Cool Clock Android Dev Intro

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 )• See: http://eagle.phys.utk.edu.guidry/android/logcat.html

• Dalvic Debug Monitor ( located in sdkDir\tools\ )• Run ddms.bat to launch this useful console application• WindowOpen PerspectiveOtherDDMS ( or via command line )

• Android Virtual Device Manager ( AVD Manager )• Create multiple virtual devices w/ various Android OS/HW configurations.• Enables testing on a variety of configurations which are not available• WindowAndroid 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

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

Page 9: Cool Clock Android Dev Intro

Java SE Intro...

9

Page 10: Cool Clock Android Dev Intro

Java Introduction• The Cool Clock project was coded entirely in Java SE and XML. • Java attributes:

• Fast, mature, portable, object-oriented language• 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:

• 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 is the JVM )

C:> java MyProgram Hello, Introductory 2D Android PPT!

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

MyProgram.javapublic class MyProgram { public static void main( String [] args ) { System.out.printf( "%s\n", "Hello, Introductory 2D Android PPT!" ); }}

Page 11: Cool Clock Android Dev Intro

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 highly-integrated 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 )

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

Page 12: Cool Clock Android Dev Intro

Android App Components

12

Page 13: Cool Clock Android Dev Intro

Components of an Android App

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

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

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

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

Android App

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

Other Activity Classes• Sub-activities such as:

• PrefsActivity.java ( Preferences screen )• AboutActivity.java ( About screen )• HelpActivity.java ( Help screen )

Other Utility Classes• Other classes such as:

• Vector2d.java13

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Page 14: Cool Clock Android Dev Intro

Android App Lifecycle

14

Page 15: Cool Clock Android Dev Intro

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

App Launch onCreate()

onStop() onPause()

onResume()onStart() Activity is visible

onDestroy()

Process killed Event

Possible

Event

Activity invisible

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

Page 16: Cool Clock Android Dev Intro

Android Application Lifecycle

App Launch onCreate()

onStop() onPause()

onResume()onStart() Activity is visible

onDestroy()

Process killed Event

Possible

Event

Activity invisible

Free resources and persist

state

Fetch prior state, instantiate app elements, call

setContentView()Resume worker

threads

Pause worker threads & persist state

Another Activity to

foreground, e.g.

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

• 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

Page 17: Cool Clock Android Dev Intro

Actual logcat Trace of Cool Clock State NotificationsI/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: RunningI/System.out( 279): ClockRunThread threadState: Running

Start Cool Clock

Press Phone Menu Button

I/System.out( 279): >>>>>>>>>>>> CoolClock.onCreateOptionsMenu() <<<<<<<<<<<I/System.out( 279): ClockRunThread threadState: RunningI/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: SuspendedI/System.out( 279): ClockRunThread threadState: Suspended

Exit Preferences

Screen

I/System.out( 279): >>>>>>>>>>>> ClockPanel.notifyClockPrefsChanged() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> CoolClock.onResume() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> ClockPanel.resume() <<<<<<<<<<< ( called by CoolClock )I/System.out( 279): ClockRunThread threadState: RunningI/System.out( 279): ClockRunThread threadState: Running

Press Back Button to

show Home Screen

I/System.out( 279): >>>>>>>>>>>> CoolClock.onPause() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> ClockPanel.pause() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> CoolClock.onStop() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> ClockPanel.stop() <<<<<<<<<<<I/System.out( 279): >>>>>>>>>>>> CoolClock.onDestroy() <<<<<<<<<<<

After any onPause() event, clock run thread is suspended

and is not consuming CPU time for drawing

Initially, clock run thread is active

When Main Menu is displayed, clock run

thread remains active

After PrefsActivity has exited, clock is again

visible and run thread state is active

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

Page 18: Cool Clock Android Dev Intro

How Cool Clock Responds to State Changes

App Launch

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

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

• Instantiates ClockPanel • Calls Activity.setContentView( clockPanel )

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

• Calls ClockPanel.pause():• Sets clock run thread state to Suspended

User exits PrefsActivity

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

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

• Calls ClockPanel.notifyClockPrefsChange():• Reloads user preference values from Android• Redraws clock with updated preferences

User presses phone Back

button

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

• Calls ClockPanel.pause():• Sets clock run thread state to Suspended

• Calls ClockPanel.stop():• Stops clock run thread

Android Event Stream

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

Page 19: Cool Clock Android Dev Intro

How to reference Android resources

19

Page 20: Cool Clock Android Dev Intro

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

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

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

How Android resources are referencedFrom Java From XML

Clock\res\values\strings.xml Excerpt

<string name="hide_circular_arrows_title">Hide Circular Arrows</string>

Clock\res\menu\menu.xml

Recall resource types include From Java

Recall that R is an auto-generated

Java class

1

2 From XML

1 2

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

Page 21: Cool Clock Android Dev Intro

Creating Main Menus

21

Page 22: Cool Clock Android Dev Intro

CoolClock.java:

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

CoolClock.java:

@Overridepublic 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; case R.id.exit: finish(); return true; } return false;}

Creating Main MenusClock\res\menu\menu.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>

Define Main Menu GUI

Handle Main Menu Creation

Handle Main Menu Button Presses

Result: Main Menu Screenshot

2 1

3

About Menu “Inflation”In this line of code:

inflater.inflate( R.menu.menu, menu );The MenuInflater reads menu.xml and then

programmatically adds the MenuItems to the method argument: menu 22

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Page 23: Cool Clock Android Dev Intro

Introduction to Android Preferences

23

Page 24: Cool Clock Android Dev Intro

Android Preferences Overview

The main menu is displayed when the

user presses the device’s Menu button

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

Pressing the Settings button displays the

Preferences/Settings menu

( The Preferences screen GUI layout/content is

defined in settings.xml )

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

Page 25: Cool Clock Android Dev Intro

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:

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

Primary Preferences Screen

Clock Mode Options Minute Display Mode Options

Hand Linked to Circular Arrows Options

Page 26: Cool Clock Android Dev Intro

settings.xml ( Defines Preferences GUI layout and content )

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

Page 27: Cool Clock Android Dev Intro

How Preferences Menus Work - DetailsPresses

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

User

Android

Phone Menu button

Calls (1st time)

Android Displays Main menu

PressesUser Settings button 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; ... } return false;}

AndroidCalls

Android Displays Preferences screen

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

( R.menu.menu )

Tells Android to launch Preferences screen

ClosesUser Preferences screen

AndroidCalls

In main activity ( CoolClock.java )protected void onActivityResult( int requestCode, int resultCode, Intent data ) { if ( requestCode == PREFS_ACTIVITY_ID ) { final ClockState newCs = PrefsActivity.getStoredPrefs( this ); clockPanel.notifyClockPrefsChanged( newCs ); }}

Notifies ClockPanel of user Preferences state changes

Reads updated Preferences

User Interacts with Preferences screen

27

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Time

Page 28: Cool Clock Android Dev Intro

• Stored Preferences values are read using { ParamKey, DefaultValue } pairs.• Example of how to retrieve Android-persisted Preferences values:

Retrieving Preferences Values

PrefsActivity.java ( excerpts )

public final class PrefsActivity extends PreferenceActivity {

public static final boolean HIDE_SECOND_HAND_DEFAULT = true;

// Key values for accessing parameters from standard Android Preferences/Settings sub-system private static final String HIDE_SEC_HAND_KEY = "hide_sec_hand"; private static final String ARC_MODES_KEY = "arc_modes"; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); addPreferencesFromResource( R.xml.settings ); } ...retrieving Preferences parameter values...

// Get a SharedPreferences object SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( cc );

// 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 );

}

Reference to settings.xml

Default parameter value

Parameter key

{ ParamKey, DefaultValue } pair

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

Page 29: Cool Clock Android Dev Intro

Creating Simple TextView Based Activities

29

Page 30: Cool Clock Android Dev Intro

android.widget.TextViewTextView extends android.view.View

CoolClock.java (excerpt)@Overridepublic boolean onOptionsItemSelected( MenuItem mi ) { ... switch ( mi.getItemId() ) { case R.id.about: startActivity( new Intent( this, AboutActivity.class ) ); return true; } .... return false;}

Creating a Simple TextView ActivityAbout Panel

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

public class AboutActivity extends Activity { @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.about ); } }

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" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/about_text" /></ScrollView>

Define GUI layout

Start activity

Define activity class

Activity’s GUI layout

Here we are starting the AboutActivity when a main menu item is pressed, but Activities can be started at

any time.

About screen’s text content

Note nesting of GUI components

strings.xml (excerpt) <string name="about_text">\CoolClock v0.8\n\Copyright (c) Richard Creamer 2011\n\All Rights Reserved\n\Email: [email protected]\n\n\CoolClock can run in three modes:\n\n\1. Learn Mode:\n\n\This mode helps teach children how to read an analog clock.\n\n\Children can drag clock hands and watch:\n\n\ - The other hands move\n\ - How the digital time changes\n\ - How the "phrase" time changes\n\ - \"Before\" and \"after\" circular arrows\n\n\2. Clock Mode:\n\n\In this mode, CoolClock is just a normal analog clock - it displays the current time and runs.\n\n\3. Modern Clock:\n\n\In this mode, a more modern clock is drawn, without numbers or minute ticks.</string>

1

2

3

4 Define text content30

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Page 31: Cool Clock Android Dev Intro

Custom 2D Graphical Components

31

Page 32: Cool Clock Android Dev Intro

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

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

Page 33: Cool Clock Android Dev Intro

GraphicsTest1 - Key Java Source Code Elementspackage ...import ...

public class GraphicsTest1 extends Activity {

... attributes...

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

// Nested class that fills its bounds with random spheres

private static class GraphicsView extends View {

... attributes...

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

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

} // End nested GraphicsView class

} // End GraphicsTest1 class

GraphicsTest1 sets its content view to

an instance of GraphicsView

GraphicsView overrides onDraw()

1

2

3

Creating an Activity using a custom 2D component

1.Create new class which extends android.view.View2.New class must override onDraw()3.onDraw() does all drawing4.Activity.setContentView( custom 2D component )

4

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...Do all drawing: background +

spheres/graphics

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

Page 34: Cool Clock Android Dev Intro

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 ); int yc = rand.nextInt( h ); int r = rand.nextInt( ( w + h )/16 ); int red = rand.nextInt( 255 ); int green = rand.nextInt( 255 ); 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; Paint paint = new Paint( Paint.ANTI_ALIAS_FLAG ); final float highlightOffsetInPct = 0.4f; float highlightOffset = highlightOffsetInPct * radius; float highlightXc = xc - highlightOffset; float highlightYc = yc - highlightOffset; final float radiusScaleFactor = 3; float highlightRadius = radiusScaleFactor * highlightOffset; RadialGradient rg = new RadialGradient( highlightXc, highlightYc, highlightRadius, 0xffffffff, color, Shader.TileMode.MIRROR ); paint.setShader( rg ); canvas.drawCircle( xc, yc, radius, paint ); }

} // End GraphicsView class

} // End GraphicsTest1 class

GraphicsTest1 - Complete Java Source Codepackage 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 {

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

// Nested class that fills its bounds with random spheres

private static class GraphicsView extends View {

private final int maxCircles = 150, minCircles = 5; private final int c1 = 0xff4466aa, c2 = 0xff112244; private final Random rand = new Random( System.currentTimeMillis() );

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

@Override protected void onDraw( Canvas canvas ) { drawRandSpheresAndBkg( canvas ); }

GraphicsTest1 sets its content view to be an

instance of GraphicsView

GraphicsView overrides onDraw()2

1

3

Such methods can be computationally expensive and should not be done in the main GUI thread! Run expensive tasks in another thread! Then, post results back to GUI thread using a

Handler.

4

Do all drawing: gradient paint background +

spheres

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

Page 35: Cool Clock Android Dev Intro

Custom 2D View Components - Overview

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

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• Override 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

Page 36: Cool Clock Android Dev Intro

Handling Touch Events

36

Page 37: Cool Clock Android Dev Intro

Overriding onTouch() @Overridepublic 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;

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; } return false;} 37

Copyright (c) Richard Creamer 2011 - All Rights Reserved

Begin a hand drag...

Terminate a hand drag...

Move the current drag hand...

ClockPanel must implement the OnTouchListener interface in order to

receive touch input

Page 38: Cool Clock Android Dev Intro

Graphics techniques used in Cool Clock

38

Page 39: Cool Clock Android Dev Intro

Cool Clock’s Graphics LayersBackground

LinearGradient shader mirroring = true

Fill clock oval Fill clock oval, clipped, with LinearGradient

shader

Add hour and minute numbers + minute ticks

• 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

( This is what is cached )

39

Copyright (c) Richard Creamer 2011 - All Rights Reserved

• Circular arrows fill/edges• Circular arrow labels

Page 40: Cool Clock Android Dev Intro

//Copyright (c) Richard Creamer 2003 - 2011 - All Rights Reserved

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

public final class Vector2d { public float x, y; public Vector2d( final float x, final float y ) { this.x = x; this.y = y; } public synchronized Vector2d rotateClockwise( final float angle ) { rotateCounterClockwise( -angle ); return this; } public synchronized Vector2d rotateCounterClockwise( final float angle ) { float cos = ( float ) Math.cos( angle ); float sin = ( float ) Math.sin( angle ); float newX = cos * x + sin * y; float newY = -sin * x + cos * y; x = newX; y = newY; return this; } 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 ); }}

Utility Class: Vector2d (selected methods)

Complete Vector2d public interface:

public float x, y;public Vector2d();public Vector2d( final float x, final float y );public Vector2d( final Vector2d v );public Vector2d( final PointF p1, final PointF p2 );public Vector2d setComponents( final float x,final float y );public float length();public float dotProduct( final Vector2d v );public float cosAngle( final Vector2d v );public Vector2d zero();public Vector2d add( final Vector2d v );public Vector2d mult( final float factor );public Vector2d div( final float quotient );public Vector2d rotateClockwise( final float angle );public Vector2d rotateCounterClockwise( final float angle );public Vector2d setLength( final float length );public Vector2d normalize();public Vector2d parallelOfLength( final float length );

CG Rotation Matrixx’y’

xy=

cos ѳ sin ѳ-sin ѳ cos ѳ

40Copyright (c) Richard Creamer 2002 - 2011 - All Rights Reserved

Page 41: Cool Clock Android Dev Intro

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

private Path getRhsArrow( float a1, float a2 ) { // Almost complete & accurate... float r1 = Innermost arrowhead side point radius float r2 = Annulus inner radius float r3 = Annulus center radius float r4 = Annulus outer radius float r5 = Outermost arrowhead side point radius float deltaAInDeg = toDegrees( F.abs( a1 - a2 ) ); Path p = new Path(); Vector2d v = new Vector2d( 0, -r3 ); p.moveTo( xc + v.x, yc + v.y ); v.rotateClockwise( arrowheadAngle ).setLength( r5 ); p.lineTo( xc + v.x, yc + v.y ); v.setLength( r4 ); 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;}

Drawing Shapes Using Paths0

12

43

5

6

78

9

( xc, yc )Use Vector2d class to handle math( previous slide )

method = Path methods method = Vector2d methods

Yellow arrows are Vector2d

rotation angles

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

Page 42: Cool Clock Android Dev Intro

Drawing Text

42

Page 43: Cool Clock Android Dev Intro

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

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

Glyphbaseline

(-) top

bottom ( bottom - top )

Most Useful Attributes

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

Page 44: Cool Clock Android Dev Intro

Drawing Text - CodeGeneral-purpose text drawing method supporting horizontal and vertical alignment

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

public enum TextVertAlign { Top, Middle, Baseline, Bottom }; // Enumeration to represent 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 ) { case Top: textY = y - r.top; // Recall that r.top is negative break; case Middle: 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}

Page 45: Cool Clock Android Dev Intro

Text Drawing Example

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

// Excerpts from TextDrawing demo...public enum TextVertAlign { Top, Middle, Baseline, Bottom };private float fontScaleFactor = 0.08f; // Crude scaling to displayprivate float crosshairScaleFactor = 0.05f; // Crude scaling to displayprivate int numTextColumns = Paint.Align.values().length;private int numTextRows = TextVertAlign.values().length; private float crosshairSize = 10f;

@Overrideprotected 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; } }

Top

Middle

Baseline

Bottom

Left Center Right

Page 46: Cool Clock Android Dev Intro

Optimization &Threading Considerations

46

Page 47: Cool Clock Android Dev Intro

Optimization• Because onDraw() is called frequently and because drawing the Cool Clock graphics is computationally expensive, two techniques were used to reduce CPU usage and to maintain a responsive GUI:

• Cache the screen bitmap ( background + clock face - hands - highlights - circ. arrows )• 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 drawn 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 the hands, the circular arrows, highlights, and the 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 the bitmap is not ready, onDraw() simply fills the screen with the background paint.• When the bitmap computation is completed and the background 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()

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

Page 48: Cool Clock Android Dev Intro

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 ) { ... } }

Skeleton Definition of Background Worker Thread

Copyright (c) Richard Creamer 2011 - All Rights Reserved

48

Page 49: Cool Clock Android Dev Intro

private void callRenderer() { // Called only in onDraw() - returns immediately after starting background worker thread

final ClockPanel cp = this; Thread t = new Thread( new Runnable() {

public void run() {

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 ) { } }

// 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(); rp.bm.set( newBitmap ); computingBitmap = false; invalidate(); } } ); } } ); // End thread definition

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

Invoking the ClockFaceRendererTask

Start background thread

Wait for worker thread to complete

Post task to GUI Handler to adopt newly computed bitmap

Note: A java.util.concurrent.atomic class is used here:AtomicReference<Bitmap> used in onDraw()

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

Page 50: Cool Clock Android Dev Intro

The Builder Pattern

50

Page 51: Cool Clock Android Dev Intro

Builder Pattern Use

Reinstate the previous field values, then call buildClockPanel() to construct ClockPanel

instance.

Calls private ClockPanel constructor

Note “dot” operator use

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

Page 52: Cool Clock Android Dev Intro

The ClockPanel Constructor

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}

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

The only ClockPanel constructor accepts a Builder object:

Page 53: Cool Clock Android Dev Intro

Builder Pattern Implementation

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

Page 54: Cool Clock Android Dev Intro

Cool ClockHelping children to read analog clocks

An introduction to developing custom 2D graphics Android applications

Richard Creamer

2to32minus1 -at- gmail -dot- comhttp://sites.google.com/site/rickcreamerhttp://www.linkedin.com/in/rickcreamer

http://sites.google.com/site/coolappssoftware

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