Creating Custom Fields and Managers is Easier than You Think!

Preview:

DESCRIPTION

Creating Custom Fields and Managers

Citation preview

Creating Custom Fields and Managers is Easier than You Think!Terrill Dent, Michael Brown

DEV 19September 27, 2010

Session Surveys

• Remember to complete your breakout session evaluation in one of two ways:

– On your BlackBerry® smartphone – use the DEVCON 2010 mobile guide, from Pyxis

– Login to My Scheduler at one of the Cyber Zone locations or on your PC

ABOUT US

SOURCE CODE ONLINEVisit www.blackberrydevcon.com for download

18Realistic

Interfaces

19Custom Fields and Managers

20Custom Layouts

21Branding

DEMOa taste of things to come

Agenda

• Fields– Custom Button

– Slider

• Managers– Toolbar

– Two Column Manager

Terrill and I were thinking that I would present Dev 21 by myself (we are both listed currently). I don’t know if that’s something you need to update on your end

FIELD BASICSa quick overview

Fields

A few things to consider when writing a custom field:

1. Constructor

2. Font

3. Layout

4. Paint

5. Input Handling (clicks, touches, keyboard, navigation)

Fields

A few things to consider when writing a custom field:

1. Constructor

2. Font

3. Layout

4. Paint

5. Input Handling (clicks, touches, keyboard, navigation)

Visual aspects

Fields

A few things to consider when writing a custom field:

1. Constructor

2. Font

3. Layout

4. Paint

5. Input Handling (clicks, touches, keyboard, navigation)

Interaction aspects

CREATING A BUTTONvisual aspects

ButtonThe Goal

• Text surrounded by an attractive border

• Different visual appearance based on state– Unfocused

– Focused

– Pressed / Active

ButtonThe Goal

Button1) Constructor

The Field constructor takes a style parameter.

Some styles are used internally by the field:

– Editability [EDITABLE , READONLY]

– Size [ USE_ALL_WIDTH, USE_ALL_HEIGHT ]

Some styles are used by the field’s manager:

– Position [ LEFT, TOP, RIGHT, BOTTOM, etc]

– Focusability [ FOCUSABLE, NON_FOCUSABLE]

Button1) Constructor

• Our constructor takes the button text, three borders and text colours for the various states, and a style:public CustomButtonField(

String text, Border normalBorder, Border focusBorder, Border activeBorder, int normalColor, int focusColor, int activeColor,long style )

• We will paint the border and background of the button ourselves– This lets us change them whenever the state of the button changes

Button2) Font

• The applyFont() method will be called if the user changes their default system font while your application is running

• You can derive a specific font to use instead of the system font

public void applyFont() {_font = Font.getDefault().derive( Font.BOLD );

}

Font.getAdvance()Beware!

Font.getBounds()Useful

Font.measureText()Very complicated…

Typically getBounds() is good enough...

abcd

Button2) Font – Measuring Text

abcd

Button3) Layout – The Box Model

Margin

Border

Padding

Content

Button3) Layout

Margin

Border

Padding

Content

In layout() we only have one task:

set the size of the content area

Padding and Borders are handled by our Manager

Button3) Layout

• The layout method must call setExtent()– We need to determine the size of the field based on the maximum

dimensions provided by our manager

protected void layout( int width, int height ) {setExtent(Math.min( width, getPreferredWidth() ),Math.min( height, getPreferredHeight() ) );

...

}

Button3) Layout

• We implement the helper methods getPreferredWidth() and getPreferredHeight()

– Some managers may use these values as “hints” so it is polite to implement them properly

public int getPreferredWidth() {

return isStyle( USE_ALL_WIDTH ) ? Integer.MAX_VALUE: _borderWidth + getFont().getBounds( _text );

}

public int getPreferredHeight() {

return _borderHeight + getFont().getHeight();}

Here we take care to respect the USE_ALL_WIDTH style bit

Allows for a button that is as wide as the possible, or just as wide as its text

Button4) Paint

• In paint() we paint the content area of the field– In our case, this is just the button text, with the proper color

protected void paint( Graphics g ){

int oldColour = g.getColor();try {

setTextColor( g );g.drawText(

_text, 0, _backgroundRect.y, DrawStyle.HCENTER, _contentRect.width );

} finally {g.setColor( oldColour );

}}

Button4) Paint

• In paintBackground() we paint the area around the text– In this example, this includes the border and the backgound since we

are drawing them ourselves

– In general, the framework will draw the border and background for us, so this step is often not necessary

protected void paintBackground( Graphics g ){Border currentBorder = getCurrentBorder();Background currentBackground = getCurrentBackground();

currentBorder.paint( g, _borderRect );currentBackground.draw( g, _backgroundRect );

}

CREATING A BUTTONinteraction aspects

Button5) Input Handling

• Many different types of trackpad input, but for a button, they all mean just about the same thing…

keyChar()

navigationClick()

trackwheelClick()

invokeAction()

• To find out when the button was clicked, register as a FieldChangeListener

public void clickButton() {fieldChangeNotify( 0 );

}

Button5) Input Handling

• Down / Click Event– When the user touches the button, activate Pressed state

• Up / Unclick Event– When the user lets go, deactivate Pressed state, click the button, and

fire the listener events

– Need to be a little bit careful not to fire the listener events multiple times – some devices send both Up AND Unclick events

ButtonDirty State

• Each field has a dirty state used to check whether the device should prompt about unsaved changes to an editable screen

• Buttons shouldn’t ever be dirty…

public void setDirty( boolean dirty ) {}

public void setMuddy( boolean muddy ) {}

• or…

public boolean isDirty() { return false; }

ButtonFinished Result

• The CustomButtonField is a flexible, full featured, bitmap based Button

• Supports custom Focus and Pressed States

• Supports USE_ALL_WIDTH and custom padding on a per-field basis

CREATING A SLIDERvisual aspects

SliderThe Goal

SliderThe Goal

• A ball that slides along a track

• A finite number of “notches” that the ball can occupy

• Different visual appearance based on state– Unfocused

– Focused

– Pressed / Active

SliderPieces of a Slider

Base

Progress

Thumb

SliderPieces of a Slider

Condense these graphics

Leave enough content in the middle to tile

We need two more full image sets for the Focused and Pressed states

SliderPieces of a Slider

Base

Progress

Thumb

SliderLayout

Start with the available spacelayout( int width, int height )

Preferred width is all available width

SliderLayout

Start with the available spacelayout( int width, int height )

Preferred width is all available width

preferredWidth = availableWidth

SliderLayout

Preferred height is the max of: - Height of the Thumb - Height of the Progress- Height of the Base

preferredWidth = availableWidth

SliderLayout

preferredWidth = availableWidth

preferredHeight = max image height

• These values are passed to setExtent() during layout

SliderPaint

Graphics.drawBitmap()Graphics.tileRop()

• The thumb position is calculated from the current “notch”

• The set of images painted depends on the current visual state

• When the visual state needs to change, we can call invalidate() to trigger a repaint

SliderInteraction

Navigation MovementEvents

Trackpad / TrackballTouchscreen

Click EventsDown / Move Events

Up Events

SliderTouchscreen Interaction

• Down Event– When the user touches the slider, activate Pressed state

• Move Event– When the user drags left or right, change notch and notify field change

listeners

• Up Event– When the user lets go, deactivate Pressed state

SliderHandle Touch Event

case TouchEvent.CLICK:case TouchEvent.DOWN:

if( touchEventOutOfBounds( message ) ) {return false;

}// fall through

case TouchEvent.MOVE:_pressed = true;setValueByTouchPosition( message.getX( 1 ) );fieldChangeNotify( 0 );return true;

case TouchEvent.UNCLICK:case TouchEvent.UP:

_pressed = false;invalidate();return true;

default:return false;

SliderSetting Notch By Touch Position

• The width of the entire slider is given by getContentWidth()

• Clamp the x coordinate of the touch between 0 and the width

• Think of the various notches as “buckets” and figure out which bucket the x coordinate falls into

• The thumb image ends up following the user’s finger

SliderTrackpad Interaction

• Trackpad must be used to– Navigate between screen elements

– Change the slider position

• Two options– Click to enter ‘edit mode’

– Interpret vertical movement as navigation and horizontal movement as position change

• Pros and cons of each…– Dedicated edit mode requires an extra click

– Consuming all horizontal movement makes assumptions about the overall screen layout

SliderClick to Edit

• Many different types of input…

keyChar()

navigationClick()

trackwheelClick()

invokeAction()

• May choose to be selective as to which keys activate the Pressed state

– Perhaps only react to SPACE or ENTER, for example

public void togglePressed() {_pressed = !_ pressed;invalidate();

}

SliderHandle Navigation Event

• When in edit mode, intercept navigation movements, change state, and notify field change listeners

– Otherwise, just pass them up to the superclass to handle

boolean navigationMovement( int dx, int dy, int status, int time ) {

if( _pressed ) {if( dx > 0 || dy > 0 ) {

incrementValue();} else {

decrementValue();}fieldChangeNotify( 0 );return true;

}return super.navigationMovement( dx, dy, status, time );

}

SliderFinished Result

MANAGER BASICSa quick overview

Managers

A few things to consider when writing a custom manager:

1. Constructor

2. Layout (most of the work)

3. Navigation

The Box Model: Margins

Margin

Border

Padding

Content

The Box Model: Margins

Margins collapse

The Box Model: Margins

Margins collapse by the smallest amount

CREATING A TOOLBARby request from last year…

Toolbar Types

Equal Space

Evenly Spaced

Toolbar Types

Equal Space

Evenly Spaced

CREATING AN EQUAL SPACE TOOLBAR

Toolbar1) Constructor

• Manager extends Field, so their constructors are similar

• Managers also take a style bit

• Some managers use the style bit to control scrolling behaviour– Our toolbar will not scroll

– We don’t have to worry about the SCROLL styles…

Toolbar2) sublayout()

• In sublayout() we need to allocate space for our children and position them within our content area

• Two important helper methods– layoutChild()

– setPositionChild()

• Typically, we keep track of remaining space as we iterate through our children and lay them out

– Each child is only allowed to use the space its older siblings have not yet consumed

– But what if the eldest sibling is greedy and takes all the space?

– (see Dev 20 for one possible solution)

Typical sublayout() procedure

• Determine how much space the child can use

• Call layoutChild(), passing in the maximum size for the child– layoutChild() looks after handling border and padding for us

• Call child.getWidth() and child.getHeight() to determine how much space the child actually used

• Determine where the child belongs relative to the others and position it using setPositionChild()

Margin Support

• A common idiom for horizontal layout (for example)thisLeftMargin = Math.max(

child.getMarginLeft(),prevRightMargin );

. . .

layoutChild( child, x + thisLeftMargin, ... );

. . .

prevRightMargin = child.getMarginRight();

Field Alignment Support

• A common idiom for horizontal layout (for example)long valign = field.getStyle() & FIELD_VALIGN_MASK;

if( valign == FIELD_BOTTOM ) {

y = managerHeight- child.getHeight() – child.getMarginBottom();

} else if( valign == FIELD_VCENTER ) {

y = child.getMarginTop() + ( managerHeight - child.getMarginTop()

- child.getHeight() - child.getMarginBottom() ) / 2;

} else { // valign == FIELD_TOP

y = currentField.getMarginTop();

}

Margins and Alignment

• Margins and alignment together are a little tricky to figure out– Possibly multiple “correct” interpretations of how they should interact

• For example, which one is correct?

Content is centeredBut what if bottom margin gets larger?

Field is centered including marginsBut content is not centered

FIELD_VCENTER

Toolbar2) sublayout()

• For our Equal Space toolbar, we might be able to make some assumptions about our children to simplify things

– No child has a margin

– All children are the same height and each child will use all the width we give it, so alignment is irrelevant

• We could check these conditions when our children are added, if we wanted to be particularly careful

– Make sure they are all instances of a special class like ToolbarButtonfor example

Toolbar2) sublayout()

protected void sublayout( int width, int height ) {

int buttonWidth = width / numFields;int maxHeight = 0;

for( int i = 0; i < numFields; i++ ) {

Field button = getField( i );

layoutChild( button, buttonWidth, height );

setPositionChild( button, i * buttonWidth, 0 );

maxHeight = Math.max( maxHeight, button.getHeight() );

}

setExtent( width, maxHeight );

}

Toolbar3) Focus Navigation

• Can we prevent vertical input from causing horizontal movement?

– The automatic translation of vertical to horizontal movement is a vestige left over from the trackwheel days…

protected int nextFocus( direction, axis ){

if( axis == AXIS_VERTICAL ||

axis == AXIS_SEQUENTIAL ) {

return -1;

}

return super.nextFocus( direction, axis );

}

ToolbarFinished Result

CREATING A TWO COLUMN MANAGERlining things up

Two Column ManagerThe Goal

Two Column ManagerThe Goal

• A set of fields arranged vertically

• Each field may be divided into two horizontal parts

• Determine an appropriate place for the division between the parts so the fields line up in columns

• Respect alignment and margins of the fields– Margins between cells won’t overlap (for simplicity) but will be

respected within each cell

Basic Strategy

• Create a custom Two Column Field (actually a Manager) that will share the layout work with the Two Column Manager

• The layout work becomes a little more involved1. Lay out the left part of each child

2. Figure out how wide to make the left column based on the size required by each child

3. Lay out the left and right part of each child

Before

Layout the Left Fields

Widest Left Field

Layout Everything

After

Two Column Manager

• Can operate as a basic vertical manager

• Lays out children top to bottom, respecting margins and alignment

• During layout, it looks for children that are TwoColumnFields

• Performs some specialized work for each one so that they are aligned in columns

Two Column Field

• Takes a left field and a right field– No reason these couldn’t be managers!

– Allows for some complicated nested structures

• Each of these fields is optional– If one isn’t provided, we create a NullField for convenience

• Two layout-related methods– layoutLeft() lays out only the left field and returns the width it would

like to consume

– sublayout() assumes that the left column width has been determined and lays out both fields accordingly

TwoColumnManager.sublayout()

• For each child that is a TwoColumnField, call layoutLeft()

• Keep track of the widest desired left column– Optionally restrict it to some maximum value so the left column can’t

take up the entire width

• For each child that is a TwoColumnField, set the left column width

• Go through all of the children and perform a ‘typical’ vertical layout

– Respect the margins and alignment of the children

TwoColumnField.layoutLeft()

• Make sure we deduct the space required by the left field’s margins

• Simply call layoutChild()

• Return the desired width of the entire left column

width = width - marginLeft - marginRight;

height = height - marginTop - marginBottom;

layoutChild( _leftField, width, height );

return marginLeft + _leftField.getWidth() + marginRight;

TwoColumnField.sublayout()

• The last piece of the puzzle!

• Calculates the width and height available for each column using the left column width provided by the manager

• Calls layoutChild() on each field to lay them out…

layoutChild( _leftField, leftWidth, leftHeight );

layoutChild( _rightField, rightWidth, rightHeight );

TwoColumnField.sublayout()

• …then positions them (just one example)long halign = field.getStyle() & FIELD_HALIGN_MASK;

if( halign == FIELD_RIGHT ) {

leftX = _leftColumnWidth- _leftField.getWidth() - leftMarginRight;

} else if( halign == FIELD_HCENTER ) {

leftX = leftMarginLeft+ ( _leftColumnWidth - leftMarginLeft

- _leftField.getWidth() - leftMarginRight ) / 2;

} else { // valign == FIELD_LEFT

leftX = leftMarginLeft;

}

• Nothing complicated here, just a bit tedious!

Two Column ManagerThe Result

OPTIMIZING LAYOUT AND PAINTknowing what happens when

Optimizations

ConstructorCache bitmaps, strings, and other static content

applyFont()Derive fontsMeasure text

layout()Cache dimensionsCalculate internal positioning

paint()Manipulate Graphics objectUse cached values to paint content

Happens Once

Happens Often

Field Created

User Changes Font

Field Added / RemovedDevice Rotated

Screen ScrollsFocus Changes

SESSION SURVEY

We value your opinion about the content in this talk and how it was presented.

See us after the talk and say “More like this!” or “I’d like to see something else!”

Please fill out the session survey.

Thank YouTerrill Dent, Michael Brown

DEV 19September 27, 2010

Recommended