Back to project page BestBoard.
The source code is released under:
MIT License
If you think the Android project BestBoard listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package digitalgarden.bestboard; /*from ww w . ja v a 2 s.c o m*/ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Rect; import digitalgarden.magicmerlin.scribe.Scribe; import digitalgarden.magicmerlin.utils.Screen; /** * BoardDescriptor class describes the soft-keyboard. * It has two sources: * - populates all the board parameters from a description file; * - get information about the actual screen and device. * Provided data and services: * - created soft-keyboard layouts (bitmaps) * - key-drawing on a canvas * - touchCode (identified by coordinate-pairs) * - boardCode (identified by touchCode, and othe data) * BoardDescriptor will not detect touches or generate key-events. */ public class Board { /** ** PREFERENCES - settings per device **/ /** * Size of the outer rim on buttons. * Touch movement (stroke) will not fire from the outer rim, but touch down will do. */ public int outerRimPercent = 20; /** ** CLASS VARIABLES - mostly loaded from descriptor file **/ /** * The board numbers LAYOUT layouts. All layouts has exactly identical dimensions, * identical number of buttons, but one button-position can contain different buttons in different layouts. * Not all of the layouts should be in use. * Layout 0 is the normal layout, Layout 1 is the shifted pair. * Alt layouts (2, 4, 6...) also has shifted pairs (3, 5, 7... respectively) */ public final static int LAYOUTS = 8; /** board width in pixels (equals to screen's lower diameter) */ private int boardWidthInPixels; /** board hight in pixels (calculated from width) */ private int boardHeightInPixels; /** number of full hexagons in one row of the board */ private int boardWidthInHexagons; /** number of hexagon rows (lower quater is missing from the bottom row) */ private int boardHeightInHexagons; /** number of half hexagons in one row (mostly this value is used for calculations) */ private int boardWidthInGrids; /** number of quater hexagon rows (mostly this value is used for calculations) */ private int boardHeightInGrids; /** 0 if odd rows aligned to the left, 1 if odd rows aligned to the right */ private int oddRowsAlignOffset; private Paint shortLabelPaint = new Paint(); private int shortLabelOffset; private Paint longLabelPaint = new Paint(); private int longLabelOffset; private Paint upperShortLabelPaint = new Paint(); private int upperShortLabelOffset; private Paint upperLongLabelPaint = new Paint(); private int upperLongLabelOffset; private Paint lowerShortLabelPaint = new Paint(); private int lowerShortLabelOffset; private Paint lowerLongLabelPaint = new Paint(); private int lowerLongLabelOffset; /** Hexagons fill paint will be set in constructor, color is variable */ private Paint hexagonFillPaint = new Paint(); /** Hexagons stroke paint will be set in constructor, color is variable */ private Paint hexagonStrokePaint = new Paint(); /** Map contains the touchCodes for all layouts */ private Bitmap layoutMap; /** Describe one button. Class variables can be reached directly from Board class. */ private class ButtonDescriptor { private int columnInHexagons; private int rowInHexagons; private int color; private String label; private int labelColor; private String upperLabel; private int upperLabelColor; private char boardCode; } /** Buttons of the layouts - will be initialized in constructor */ private ButtonDescriptor[][] buttons; /** * Areas without button will give EMPTY_TOUCH_CODE. * More buttons than EMPTY_TOUCH_CODE cannot be definied. */ public final static int EMPTY_TOUCH_CODE = 0x3FF; /** Layout skin for the current layout. */ private Bitmap layoutSkin = null; /** This layout can be found in the layoutSkin bitmap (when bitmap is not null) */ private int layout; /** * Calculates touchCode from the hexagonal position. * Important, that the touchCodes should be identical in all layouts and in the map. * TouchCodes are the indexes of button[] in LayoutDescription. * @param hexagonCol column of the button (x coord in hexagons) * @param hexagonRow row of the button (y coord in hexagons) * @return touchCode of the button */ private int touchCodeFromPosition( int hexagonRow, int hexagonCol ) { return hexagonRow * boardWidthInHexagons + hexagonCol; } /** ** CONSTRUCTOR **/ /** * The number of buttons on one layout is maximalized * The 'last' code (which can be represented on the map) determines the maximum number of buttons */ public static final int MAX_BUTTONS = EMPTY_TOUCH_CODE; /** Maximal layout width in hexagons */ public static final int MAX_BOARD_WIDTH_IN_HEXAGONS = 24; /** Maximal layout height in hexagons */ public static final int MAX_BOARD_HEIGHT_IN_HEXAGONS = 12; /** * True if a board can be created with these parameters. * This can be checked before creating board or constructor will use the same method */ public static boolean isValidDimension( int boardWidthInHexagons, int boardHeightInHexagons ) { if ( boardWidthInHexagons < 1 || boardWidthInHexagons > MAX_BOARD_WIDTH_IN_HEXAGONS ) return false; if ( boardHeightInHexagons < 1 || boardHeightInHexagons > MAX_BOARD_HEIGHT_IN_HEXAGONS ) return false; if ( boardWidthInHexagons * boardHeightInHexagons > MAX_BUTTONS ) return false; return true; } /** * Constructor needs data to generate a keyboard with button-holes. * It needs information about the screen (context) and about the measures of the board. * All specific data (buttons etc.) will be added later. * @param context context to get the exact screen resolution * @param boardWidthInHexagons width in full hexagons * @param boardHeightInHexagons height in full hexagons * @param portrait true: portrait, false: landscape * @param oddRowsAlignedLeft odd rows aligned to the left * @throws IllegalArgumentException if board cannot be created with this dimension * Dimension can be checked previously with isValidDimension() */ public Board( Context context, int boardWidthInHexagons, int boardHeightInHexagons, boolean portrait, boolean oddRowsAlignedLeft ) throws IllegalArgumentException { if ( !isValidDimension(boardWidthInHexagons, boardHeightInHexagons) ) { throw new IllegalArgumentException("Board cannot be created with these arguments!"); } // NON SCREEN-SPECIFIC DATA this.oddRowsAlignOffset = oddRowsAlignedLeft ? 0 : 1; this.boardWidthInHexagons = boardWidthInHexagons; this.boardHeightInHexagons = boardHeightInHexagons; // Each row has one more half hexagon column this.boardWidthInGrids = boardWidthInHexagons * 2 + 1; // Each row has three quaters of hexagonal height; the lowest quater height is missing from the bottom this.boardHeightInGrids = boardHeightInHexagons * 3; // INITIALIZE BUTTONS' ARRAY // this two-dimensional array will be populated later // null: non-deinied (empty) button buttons = new ButtonDescriptor[ LAYOUTS ][ boardWidthInHexagons * boardHeightInHexagons ]; // GENERATE SCREEN SPECIFIC VALUES // PORTRAIT: shorter screen diameter, LANDSCAPE (optional): longer screen diameter this.boardWidthInPixels = portrait ? Screen.getShorterDiameter(context) : Screen.getLongerDiameter(context); // Board pixel height is calculated with the ratio of a regular hexagon // after this point all the measurements are calculated from pixelHeight backwards this.boardHeightInPixels = ( (boardHeightInGrids * boardWidthInPixels * 1000) / (boardWidthInGrids * 1732) ); // As PORTRAIT has to work also in LANDSCAPE mode // Board height cannot be higher as 3/4 * shorter screen diameter (screen height in landscape mode) int maximalBoardHeightInPixels = Screen.getShorterDiameter(context) * 3 / 4; // If board height exceeds maximal value, board will be distorted if ( this.boardHeightInPixels > maximalBoardHeightInPixels ) this.boardHeightInPixels = maximalBoardHeightInPixels; // CALCULATE FONT PARAMETERS // Font Size should be calculated // There are two sizes: // - one character - one grid height // - string (MMMMM) - two grid width ( it will be lower than one grid !! What if board is distrorted ?? ) Rect bounds = new Rect(); shortLabelPaint.setTextSize( 1000f ); shortLabelPaint.getTextBounds("MMMMMMM", 0, 4, bounds); // intendedHeightinPixels can be ( boardHeightInPixels / boardHeightInGrids ) // Ratio => SIZE : intendedHeightInPixels = 1000f : bounds.height() int textSize = 2000 * boardHeightInPixels / (boardHeightInGrids * bounds.height()) ; // width for 5M: textSize = 2 * 1000 * boardWidthInPixels / (boardWidthInGrids * bounds.width()); int gridHeightInPixels = boardHeightInPixels / boardHeightInGrids; shortLabelOffset = gridHeightInPixels * 10 / 20; // 6 shortLabelPaint.setTextSize( textSize * 10 / 20 ); // 6 shortLabelPaint.setTextAlign( Align.CENTER ); shortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG ); longLabelOffset = gridHeightInPixels * 9 / 20; // 5 longLabelPaint.setTextSize( textSize * 7 / 20 ); // 5 longLabelPaint.setTextAlign( Align.CENTER ); longLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG | Paint.FAKE_BOLD_TEXT_FLAG ); upperShortLabelOffset = gridHeightInPixels * -16 / 20; // 4 upperShortLabelPaint.setTextSize( textSize * 7 / 20 ); // 4 upperShortLabelPaint.setTextAlign( Align.CENTER ); upperShortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG ); lowerShortLabelOffset = gridHeightInPixels * 16 / 20; // 13 lowerShortLabelPaint.setTextSize( textSize * 10 / 20 ); // 5 lowerShortLabelPaint.setTextAlign( Align.CENTER ); lowerShortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG ); upperLongLabelOffset = gridHeightInPixels * -10 / 20; upperLongLabelPaint.setTextSize( textSize * 5 / 20 ); upperLongLabelPaint.setTextAlign( Align.CENTER ); lowerLongLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG | Paint.FAKE_BOLD_TEXT_FLAG ); lowerLongLabelOffset = gridHeightInPixels * 12 / 20; lowerLongLabelPaint.setTextSize( textSize * 7 / 20 ); lowerLongLabelPaint.setTextAlign( Align.CENTER ); lowerLongLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG | Paint.FAKE_BOLD_TEXT_FLAG ); // SETTING UP OTHER PAINT PARAMETERS hexagonFillPaint.setStyle( Style.FILL ); hexagonStrokePaint.setStyle( Style.STROKE ); hexagonStrokePaint.setStrokeWidth( 0f ); hexagonStrokePaint.setColor( Color.BLACK ); } /** ** DRAWINGS **/ private int getGridX( int row, int column ) { return ( (row + oddRowsAlignOffset) %2 ) + column * 2 + 1; } private int getGridY( int row ) { return row * 3 + 2; } // a "grid" a flszles/negyedmagas rcspontoknak felel meg // ez a kt fggvny a pontos pixelt adja vissza private int getPixelX( int gridX ) { return gridX * boardWidthInPixels / boardWidthInGrids; } private int getPixelY( int gridY ) { return gridY * boardHeightInPixels / boardHeightInGrids; } // A kzp rcspont alapjn rajzolunk hatszget, a krnyez? rcspontokra private Path hexagonPath( int gridX, int gridY ) { Path path = new Path(); path.moveTo( getPixelX( gridX ), getPixelY( gridY-2 ) ); path.lineTo( getPixelX( gridX+1 ), getPixelY( gridY-1 ) ); path.lineTo( getPixelX( gridX+1 ), getPixelY( gridY+1 ) ); path.lineTo( getPixelX( gridX ), getPixelY( gridY+2 ) ); path.lineTo( getPixelX( gridX-1 ), getPixelY( gridY+1 ) ); path.lineTo( getPixelX( gridX-1 ), getPixelY( gridY-1 ) ); path.close(); return path; } // A kzp rcspont alapjn rajzolunk hatszget, a krnyez? rcspontokra private Path hexagonPath( int gridX, int gridY, int pixelHalfWidth, int pixelQuaterHeight ) { int pixelX = getPixelX( gridX ); int pixelY = getPixelY( gridY ); Path path = new Path(); path.moveTo( pixelX, pixelY - 2 * pixelQuaterHeight ); path.lineTo( pixelX + pixelHalfWidth, pixelY - pixelQuaterHeight ); path.lineTo( pixelX + pixelHalfWidth, pixelY + pixelQuaterHeight ); path.lineTo( pixelX, pixelY + 2 * pixelQuaterHeight ); path.lineTo( pixelX - pixelHalfWidth, pixelY + pixelQuaterHeight ); path.lineTo( pixelX - pixelHalfWidth, pixelY - pixelQuaterHeight ); path.close(); return path; } /** ** CREATE BOARD MAP **/ private int colorFromTouchCode( int touchCode, boolean outerRim ) { // TouchCode 5 + 5 bit > R 5 bit (G) B 5 bit // R byte : 5bit << 3 + 5 // B byte : 5bit << 3 + 5 int red = ((touchCode & 0x3E0) << 14) + 0x50000; // >> 5 << 3 << 16 + 5 << 16; int green = outerRim ? 0 : 0xFF00 ; int blue = ((touchCode & 0x1F) << 3) + 5; return 0xFF000000 | red | green | blue; } private int touchCodeFromColor( int color ) { // 5 bit : byte >> 3 // R >> 16 >> 3 << 5 // B >> 3 return ((color & 0xF80000) >> 14) | ((color & 0xF8) >> 3); } private boolean outerRimFromColor(int color ) { return (color & 0xFF00) == 0; } private void createMap() { layoutMap = Bitmap.createBitmap( boardWidthInPixels, boardHeightInPixels, Bitmap.Config.RGB_565); layoutMap.eraseColor( colorFromTouchCode( 0xFF, false) ); Canvas canvas = new Canvas( layoutMap ); Paint paint = new Paint(); paint.setStyle( Paint.Style.FILL ); paint.setAntiAlias( false ); paint.setDither( false ); int gridX; int gridY; int pixelQuaterHeight = (boardHeightInPixels * (100 - outerRimPercent)) / (boardHeightInGrids * 100); int pixelHalfWidth = (boardWidthInPixels * (100 - outerRimPercent)) / (boardWidthInGrids * 100); // hatszg "sorok" for ( int row = 0; row < boardHeightInHexagons; row++ ) { // grid-kzppont magassgok gridY = getGridY( row ); // hatszg oszlopok for ( int col = 0; col < boardWidthInHexagons; col++ ) { // grid kzppont x-ek gridX = getGridX(row, col ); paint.setColor( colorFromTouchCode( touchCodeFromPosition( row, col ), false ) ); canvas.drawPath( hexagonPath(gridX, gridY), paint); paint.setColor( colorFromTouchCode( touchCodeFromPosition( row, col ), true ) ); canvas.drawPath( hexagonPath(gridX, gridY, pixelHalfWidth, pixelQuaterHeight), paint); Scribe.debug("touchCode: " + touchCodeFromPosition( row, col ) + " ret: " + touchCodeFromColor(layoutMap.getPixel(getPixelX( gridX ), getPixelY( gridY ) )) + " color: " + Integer.toHexString( colorFromTouchCode( touchCodeFromPosition( row, col ), true ) ) + " r: " + row + " c: " + col); } } } public Bitmap getMap() { createMap(); return layoutMap; } /** ** CREATE LAYOUT SKIN **/ /* * Not all layout can be stored as bitmap because of memory problems. * Now only one layout is cached in layoutSkin. * The process could be quicker, if bitmaps (same size!) could be reused. * Now a new bitmap will be generated for every bitmap. */ public Bitmap getLayoutSkin( int layout ) { if ( !isValidLayout(layout) ) layout = 0; if ( layoutSkin != null) { if ( this.layout == layout ) return layoutSkin; else layoutSkin.recycle(); } layoutSkin = createLayoutSkin( layout ); this.layout = layout; return layoutSkin; } public static final int BOARD_BACKGROUND = 0xFF777777; private Bitmap createLayoutSkin( int layout ) { Bitmap skin = Bitmap.createBitmap( boardWidthInPixels, boardHeightInPixels, Bitmap.Config.RGB_565); Canvas canvas = new Canvas( skin ); skin.eraseColor( BOARD_BACKGROUND ); for (ButtonDescriptor buttonDescriptor : buttons[layout] ) { drawButton( canvas, buttonDescriptor ); } return skin; } private void drawButton( Canvas canvas, ButtonDescriptor buttonDescriptor ) { if ( buttonDescriptor == null ) return; // index (in buttons[][index]) == touchCode (this is always true) // Theoretically from index/touchCode the buttons position can be calculated. // BUT this is NOT obligatory!! So the buttons will store their position. // Would be a better idea to store gridX/gridY coords for buttons. int gridX = getGridX( buttonDescriptor.rowInHexagons, buttonDescriptor.columnInHexagons ); int gridY = getGridY( buttonDescriptor.rowInHexagons ); hexagonFillPaint.setColor( buttonDescriptor.color ); canvas.drawPath( hexagonPath(gridX, gridY), hexagonFillPaint ); canvas.drawPath( hexagonPath(gridX, gridY), hexagonStrokePaint ); int pixelX = getPixelX( gridX ); int pixelY = getPixelY( gridY ); if ( buttonDescriptor.upperLabel == null ) { // Short-label if ( buttonDescriptor.label.length() == 1 ) { shortLabelPaint.setColor(buttonDescriptor.labelColor ); canvas.drawText( buttonDescriptor.label , pixelX, pixelY + shortLabelOffset, shortLabelPaint ); } // Long-label else { longLabelPaint.setColor(buttonDescriptor.labelColor ); canvas.drawText( buttonDescriptor.label , pixelX, pixelY + longLabelOffset, longLabelPaint ); } } else { // Lower short-label if ( buttonDescriptor.label.length() == 1 ) { lowerShortLabelPaint.setColor(buttonDescriptor.labelColor ); canvas.drawText( buttonDescriptor.label , pixelX, pixelY + lowerShortLabelOffset, lowerShortLabelPaint ); } // Lower long-label else { lowerLongLabelPaint.setColor(buttonDescriptor.labelColor ); canvas.drawText( buttonDescriptor.label , pixelX, pixelY + lowerLongLabelOffset, lowerLongLabelPaint ); } // Upper short-label if ( buttonDescriptor.upperLabel.length() == 1 ) { upperShortLabelPaint.setColor(buttonDescriptor.upperLabelColor ); canvas.drawText( buttonDescriptor.upperLabel , pixelX, pixelY + upperShortLabelOffset, upperShortLabelPaint ); } // Upper long-label else { upperLongLabelPaint.setColor(buttonDescriptor.upperLabelColor ); canvas.drawText( buttonDescriptor.upperLabel , pixelX, pixelY + upperLongLabelOffset, upperLongLabelPaint ); } } } /** * True if layout is a valid. * This can be checked before adding a button */ public static boolean isValidLayout( int layout ) { if ( layout < 0 || layout >= LAYOUTS ) return false; return true; } /** * True if this hexagonal position is a valid on this board * This can be checked before adding a button */ public boolean isValidPosition( int row, int column ) { if ( row < 0 || row >= boardHeightInHexagons ) return false; if ( column < 0 || column >= boardWidthInHexagons ) return false; return true; } public static String StringPrelude( String string, int prelude ) { if ( string == null ) return null; if ( prelude >= string.length() ) return string; if ( prelude <= 0 ) return ""; return string.substring( 0, prelude ); } /** * * @param layout * @param row * @param column * @param code * @param backgroundColor * @param label * @param labelColor * @param upperLabel * @param upperLabelColor * @return * @throws IllegalArgumentException */ public boolean addButton( int layout, int row, int column, char code, int backgroundColor, String label, int labelColor, String upperLabel, int upperLabelColor ) throws IllegalArgumentException { if ( !isValidLayout( layout ) ) { throw new IllegalArgumentException("This layout is not valid! Button cannot be added!"); } if ( !isValidPosition( row, column ) ) { throw new IllegalArgumentException("This button position is not valid! Button cannot be added!"); } boolean ret = true; ButtonDescriptor button = new ButtonDescriptor(); button.rowInHexagons = row; button.columnInHexagons = column; button.boardCode = code; button.color = backgroundColor; button.label = StringPrelude( label, 10 ); button.labelColor = labelColor; button.upperLabel = StringPrelude( upperLabel, 10 ); button.upperLabelColor = upperLabelColor; // put in its position int index = touchCodeFromPosition( row, column ); if ( buttons[layout][index] != null ) ret = false; buttons[layout][index] = button; return ret; } }