creating custom fields and managers is easier than you think!
DESCRIPTION
Creating Custom Fields and ManagersTRANSCRIPT
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