Creating Custom Fields and Managers is Easier than You Think!

Page 1

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


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.