Creating Custom Fields and Managers is Easier than You Think! Terrill Dent, Michael Brown DEV 19 September 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
18
Realistic Interfaces
19
Custom Fields and Managers
20 Custom Layouts
SOURCE CODE ONLINE Visit www.blackberrydevcon.com for download
21
Branding
DEMO a taste of things to come
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
Agenda • Fields – Custom Button – Slider
• Managers – Toolbar – Two Column Manager
FIELD BASICS a 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
Visual aspects
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) Interaction aspects
CREATING A BUTTON visual aspects
Button The Goal • Text surrounded by an attractive border • Different visual appearance based on state – Unfocused – Focused – Pressed / Active
Button The Goal
Button 1) 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]
Button 1) 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
Button 2) 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 ); }
Button 2) Font – Measuring Text
Font.getAdvance() Beware!
Font.getBounds() Useful
abcd abcd
Font.measureText() Very complicated… Typically getBounds() is good enough...
Button 3) Layout – The Box Model Margin Border Padding Content
Button 3) 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
Button 3) 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() ) ); ... }
Button 3) 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
Button 4) 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 ); } }
Button 4) 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 BUTTON interaction aspects
Button 5) Input Handling • Many different types of trackpad input, but for a button, they all mean just about the same thing… keyChar() navigationClick() trackwheelClick()
public void clickButton() { fieldChangeNotify( 0 ); }
invokeAction()
• To find out when the button was clicked, register as a FieldChangeListener
Button 5) 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
Button Dirty 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; }
Button Finished 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 SLIDER visual aspects
Slider The Goal
Slider The 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
Slider Pieces of a Slider Progress
Base
Thumb
Slider Pieces of a Slider Progress
Condense these graphics Leave enough content in the middle to tile
Base
Thumb
We need two more full image sets for the Focused and Pressed states
Slider Layout
Start with the available space layout( int width, int height ) Preferred width is all available width
Slider Layout preferredWidth = availableWidth
Start with the available space layout( int width, int height ) Preferred width is all available width
Slider Layout preferredWidth = availableWidth
Preferred height is the max of: - Height of the Thumb - Height of the Progress - Height of the Base
Slider Layout preferredWidth = availableWidth
preferredHeight = max image height • These values are passed to setExtent() during layout
Slider Paint
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
Slider Interaction Touchscreen
Trackpad / Trackball
Down / Move Events
Click Events
Up Events
Navigation Movement Events
Slider Touchscreen 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
Slider Handle 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;
Slider Setting 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
Slider Trackpad 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
Slider Click to Edit • Many different types of input… keyChar() navigationClick() trackwheelClick() invokeAction()
public void togglePressed() { _pressed = !_ pressed; invalidate(); }
• May choose to be selective as to which keys activate the Pressed state – Perhaps only react to SPACE or ENTER, for example
Slider Handle 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 ); }
Slider Finished Result
MANAGER BASICS a 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 TOOLBAR by request from last year…
Toolbar Types Equal Space
Evenly Spaced
Toolbar Types Equal Space
Evenly Spaced
CREATING AN EQUAL SPACE TOOLBAR
Toolbar 1) 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…
Toolbar 2) 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?
FIELD_VCENTER
Content is centered But what if bottom margin gets larger?
Field is centered including margins But content is not centered
Toolbar 2) 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 ToolbarButton for example
Toolbar 2) 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 ); }
Toolbar 3) 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 ); }
Toolbar Finished Result
CREATING A TWO COLUMN MANAGER lining things up
Two Column Manager The Goal
Two Column Manager The 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 â&#x20AC;˘ Create a custom Two Column Field (actually a Manager) that will share the layout work with the Two Column Manager â&#x20AC;˘ The layout work becomes a little more involved 1.
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 Manager The Result
OPTIMIZING LAYOUT AND PAINT knowing what happens when
Optimizations Happens Once
Constructor
Cache bitmaps, strings, and other static content
applyFont()
Derive fonts Measure text
layout()
Cache dimensions Calculate internal positioning
paint() Happens Often
Manipulate Graphics object Use cached values to paint content
Field Created
User Changes Font
Field Added / Removed Device Rotated
Screen Scrolls Focus 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 You Terrill Dent, Michael Brown DEV 19 September 27, 2010