Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

Create a rich UI layout with TableLayoutManager

by BlackBerry Development Advisor on ‎02-16-2010 04:07 PM - edited on ‎03-06-2012 03:47 PM by BlackBerry Development Advisor (23,117 Views)

Summary

 

This article applies to the following:

  • BlackBerry® Device Software 4.2 and later

Details

 

Cells are the basic building blocks for any layout. Each cell defines its space requirements for the Field contained within it. If you try to visualize this arrangement, it looks very much like a table. Using a TableLayoutManager, you can handle fixed or relative cell sizes. Also, by using embedded tables, you can easily build almost any layout required.

 

The full source code for a TableLayoutManager is provided at the end of this article.You can also download the source code file from here.

 

The TableLayoutManager has the following two constructors:

  • public TableLayoutManager(int columnStyles[], long style)
  • public TableLayoutManager(int columnStyles[], int columnWidths[], int horizontalPadding, long style)

Parameter Description 
columnStyles[]

Defines the styles for all the columns. The size of this array is basically the number of columns you want in a given row. The different styles you can choose are as follows:

  • USE_PREFERRED_SIZE - Allows the field to use all the space required
  • USE_PREFERRED_WIDTH_WITH_MAXIMUM - Allows the field to use as much space as required up to a maximum. You can specify the maximum width for each field by passing in the width in the columnWidths variable
  • FIXED_WIDTH - Specifies that columns are of fixed width
  • SPLIT_REMAINING_WIDTH - Specifies the remaining width to use. It can be thought of as "this column will utilize all the remaining width of the table". For example, if the TableLayoutManager has one of these columns defined as SPLIT_REMAINING_WIDTH and the manager has Manager.USE_ALL_WIDTH, the column will fill the remaining space. If there are multiple columns defined as SPLIT_REMAINING_WIDTH, the columns will evenly share that remaining width
style

The manager style. You can just choose from Manager. HORIZONTAL_SCROLL, Manager.xxxx.

columnWidths

If you choose, you can specify widths to be used for each field to force particular column size.

horizontalPadding

Allows you to specify horizontal padding between columns.


 

You can add any Field to the TableLayoutManager, including other managers (even other TableLayoutManagers) to create very complex layouts. Simply define the number of columns required and then, based on number of fields added to the layout, the TableLayoutManager will automatically align the fields in separate rows such that you do not need to define the number of rows at compile time.

 

For example, the following code shows how to create an image thumbnail preview. A table layout with five columns is created and populated from an array of images. Note that rows are automatically added as the table fills out.

 

 

 

TableLayoutManager colFMgr = new TableLayoutManager(new int[]
{
TableLayoutManager.USE_PREFERRED_SIZE,
TableLayoutManager.USE_PREFERRED_SIZE,
TableLayoutManager.USE_PREFERRED_SIZE,
TableLayoutManager.USE_PREFERRED_SIZE,
TableLayoutManager.USE_PREFERRED_SIZE
}, Manager.HORIZONTAL_SCROLL);
for(int i= 0;i < images.length;i++)
{
colFMgr.add(new BitmapField(images[i],Field.FOCUSABLE));
}
add(colFMgr);

 

TableLayoutManager - Source code

 

 

package com.myCompany.myPackage;

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.XYPoint;
import net.rim.device.api.util.Arrays;
/**
 * TableLayoutManager can be used to create multi column table
 * views. You can even embed table within another table to create
 * complex tabular views.
 */
public class TableLayoutManager extends Manager
{

    int _columnWidths[];
    int _suggestedColumnWidths[];
    int _rowHeights[];
    int _columnStyles[];

    /** Let the field use up ALL the space that it needs **/
    public static final int USE_PREFERRED_SIZE = 1;
    /** let the field use up as much space as it needs UP TO a maximum */
    public static final int USE_PREFERRED_WIDTH_WITH_MAXIMUM = 2;
    /** the fields should use up the remaining space evenly */
    public static final int SPLIT_REMAINING_WIDTH = 4;
    /** the column is fixed width **/
    public static final int FIXED_WIDTH = 8;

    private static int BITMASK_USE_PREFERRED =
            USE_PREFERRED_WIDTH_WITH_MAXIMUM
            | USE_PREFERRED_SIZE;

    public static int DEFAULT_PADDING = 5;
    int _rows;
    int _columns;
    private int _horizPadding;

    public TableLayoutManager(int columnStyles[], long style)
    {
        this(columnStyles, null, DEFAULT_PADDING, style);
    }

    /**
     * creates a table with the specified column styles
     *
     * @param columnStyles
     *           - array of styles for all the columns. The size
     *           of this array determines the number of columns.
     * @param columnWidths
     *           - array of widths. This is used for the FIXED_WIDTH,
     *           and USE_PREFERRED_WIDTH_WITH_MAXIMUM styles.
     *           the value is ignored for the other styles.
     * @param horizontalPadding
     *           - space between columns
     * @param style
     */

    public TableLayoutManager(int columnStyles[], int columnWidths[],
        int horizontalPadding, long style)
    {
        super(style);

        _horizPadding = horizontalPadding;

        _columnStyles = columnStyles;
        if (_columnStyles == null)
              throw new IllegalArgumentException("No column styles");

        if (columnWidths != null)
        {
            _suggestedColumnWidths = Arrays.copy(columnWidths,
                                     0,columnWidths.length);
            if (_suggestedColumnWidths.length < _columnStyles.length)
            {
                int oldLength = _suggestedColumnWidths.length;
                int increase = columnStyles.length - oldLength;
               _suggestedColumnWidths = Arrays.copy(columnWidths, 0,
                            columnStyles.length);
               Arrays.fill(_suggestedColumnWidths, 0,
                           oldLength, increase);
            }
        }
        else
            _suggestedColumnWidths = new int[_columnStyles.length];
    }

    private Field getField(int x, int y)
    {
        int i = x + (y * _columns);
        if (i >= getFieldCount()) return null;
        return getField(i);
    }

    private boolean isColumnStyle(int value, int flag)
    {
        return ((value) & (flag)) > 0;
    }

    /**
     * Implements the getPreferredWidth call to return the
     * expected width for this manager. The expected width is
     * the Max(Sum (Column Widths))
     */
    public int getPreferredWidth()
    {
        int numberFields = getFieldCount();
        if (numberFields == 0) return 0;
        int rows = numberFields / _columnStyles.length;
        int prefferedWidth = 0;

        int styles[] = _columnStyles;
        int[] columnWidths = new int[_columns];
        Arrays.fill(columnWidths, -1);

        for (int i = 0; i < _columns; i++)
        {
            // assign the fixed widths
            if (isColumnStyle(styles[i], FIXED_WIDTH))
            {
                columnWidths[i] = _suggestedColumnWidths[i];
            }
            else
            {
                if (isColumnStyle(styles[i], BITMASK_USE_PREFERRED))
                {
                    for (int j = 0; j < rows; j++)
                    {
                        Field field = getField(i, j);
                        if (field != null)
                        {

                            int actualWidth = getPreferredWidthOfChild(field)
                                     + field.getMarginLeft()
                                     + field.getMarginRight();
                            if (isColumnStyle(styles[i],
                                USE_PREFERRED_WIDTH_WITH_MAXIMUM))
                            {
                                actualWidth = Math.min(actualWidth,
                                  _suggestedColumnWidths[i]);
                            }

                            columnWidths[i] =
                               Math.max(actualWidth,columnWidths[i]);
                        }
                    }
                }
            }
        }

        for (int n = 0; n < _columns; n++)
        {
            prefferedWidth += columnWidths[n];
        }
        
        return prefferedWidth;
    }

    /**
     * implements the preferred height for this layout
     */
    public int getPreferredHeight()
    {
        int numberFields = getFieldCount();
        if (numberFields == 0) return 0;

        int rows = numberFields / _columnStyles.length;
        int prefferedHeight = 0;

        int[] rowHeights = new int[rows];
        Arrays.fill(rowHeights, -1);
        
        _columns = _columnStyles.length; 
        for (int i = 0; i < _columns; i++)
        {
            for (int j = 0; j < rows; j++)
            {
                Field field = getField(i, j);
                if (field != null)
                {
                int actualHeight = getPreferredHeightOfChild(field)
                            + field.getMarginBottom()
                            + field.getMarginTop();
                rowHeights[j] = Math.max(actualHeight, rowHeights[j]);
                }
            }
        }

        for (int n = 0; n < rows; n++)
        {
            prefferedHeight += rowHeights[n];
        }
        return prefferedHeight;
    }

    /**
     * Defines how Fields for this manager needs to be handled.
     */
    protected void sublayout(int layoutWidth, int layoutHeight)
    {
        int numberFields = getFieldCount();
        if (numberFields == 0) {   
        	if ( this._columnStyles != null ) { //At least one field was added and removed from this manager
        		setExtent( 0 ,  0 );
        	} 
        	return; 
        }
        layoutWidth -= getPaddingLeft() + getPaddingRight();
        layoutHeight -= getPaddingTop() + getPaddingBottom();
        _columns = _columnStyles.length;
        int styles[] = _columnStyles;
        if (isStyle(Field.USE_ALL_WIDTH))
        {
            boolean found = false;
            // if the field should take maximum space, at least
            // the last field
            // should be SPLIT_REMAINING_WIDTH
           for (int n = 0; n < _columns; n++)
           {
               if (styles[n] == SPLIT_REMAINING_WIDTH)
               {
                   found = true;
                   break;
               }
           }
           if (!found)
           {
               styles[_columns - 1] = SPLIT_REMAINING_WIDTH;
           }
        }
        _rows = numberFields / _columns;
        if ((numberFields % _columns) > 0) _rows++;
        _columnWidths = new int[_columns];  // arrays that keep track of
                                            // maximum widths
        _rowHeights = new int[_rows];

        // widths and heights are -1 if unassigned, we use this
        // fact to assign the column widths
        Arrays.fill(_columnWidths, -1);
        Arrays.fill(_rowHeights, -1);

        /*
         * there are three types of columns, fixed width, split
         * remaining width, and use preferred size step 1) we
         * need to look at the columns that are marked as "use
         * preferred size", find the widest element, then
         * record that maximum width step 2) as well, we can
         * assign the column widths for the columns that are
         * fixed width
         */

        for (int i = 0; i < _columns; i++)
        {
            // assign the fixed widths
            if (isColumnStyle(styles[i], FIXED_WIDTH))
            {
                _columnWidths[i] = _suggestedColumnWidths[i];
            }
            else
            {
                if (isColumnStyle(styles[i], BITMASK_USE_PREFERRED))
                {
                    for (int j = 0; j < _rows; j++)
                    {
                        Field field = getField(i, j);
                        if (field != null)
                        {
                            layoutChild(field, Math.max(0, layoutWidth
                               - (field.getMarginLeft()
                               + field.getMarginRight())),
                                 Math.max(0,layoutHeight
                               - (field.getMarginBottom()
                               + field.getMarginTop())));

                            int actualWidth = getPreferredWidthOfChild(field)
                                 + field.getMarginLeft()
                                 + field.getMarginRight();
                            int actualHeight = getPreferredHeightOfChild(field)
                                 + field.getMarginBottom()
                                 + field.getMarginTop();

                            if (isColumnStyle(styles[i],
                                  USE_PREFERRED_WIDTH_WITH_MAXIMUM))
                            {
                                actualWidth = Math.min(actualWidth,
                                   _suggestedColumnWidths[i]);
                            }

                            _columnWidths[i] = Math.max(actualWidth,
                                _columnWidths[i]);
                            _rowHeights[j] = Math.max(actualHeight,
                                _rowHeights[j]);
                        }
                    }
                }
            }
        }

        /*
         * step 3 - find out the total width used up by the
         * fields that have known widths
         */
        int usedColumnWidth = 0;
        int numUnassignedColumnWidths = 0;

        for (int i = 0; i < _columns; i++)
        {
            if (_columnWidths[i] >= 0)
            {
                usedColumnWidth += _columnWidths[i]
                        + ((i < (_columns - 1)) ? _horizPadding : 0);
            }
            else
            {
                numUnassignedColumnWidths++;
            }
        }

        /*
         * assign the remaining space evenly amongst the
         * unassigned columns
         */
        if (numUnassignedColumnWidths > 0)
        {
            int remainingWidthToAssign = layoutWidth - usedColumnWidth;
            if (remainingWidthToAssign < 0)
            {
                remainingWidthToAssign = 0;
            }

            int splitRemainingWidth = (remainingWidthToAssign -
                                      ((numUnassignedColumnWidths - 1)
                                      * _horizPadding))
                                         / numUnassignedColumnWidths;
            
            int[] evenlySpacedColumnPosition = new int [_columnWidths.length]; 
            Arrays.fill(evenlySpacedColumnPosition, -1);
            
            for (int i = 0; i < _columns; i++)
            {
                int assignedWidth = Math.min(remainingWidthToAssign,
                     splitRemainingWidth);
                if (_columnWidths[i] < 0)
                {
                    _columnWidths[i] = assignedWidth;
                    remainingWidthToAssign -= assignedWidth;
                    evenlySpacedColumnPosition[i] = 1; //mark the current column as assigned
                }
            }
           
            /* 
             * We should assign the rest of the integer division to the evenly spaced columns: 
             * splitRemainingWidth = remainingWidthToAssign  / numUnassignedColumnWidths  
             * */
            if ( this.isStyle(Manager.USE_ALL_WIDTH) && remainingWidthToAssign > 0 ) {
            	for (int i = 0; i < evenlySpacedColumnPosition.length; i++) {
					if ( evenlySpacedColumnPosition[i] == 1 ) {
						_columnWidths[i] += 1;
						remainingWidthToAssign--;
					}
					
					if ( remainingWidthToAssign == 0) break;
				}	 
            }
        }

        int currentRow = 0;
        int currentColumn = 0;
        int y = getPaddingTop();
        for (int n = 0; n < numberFields; n++)
        {            Field field = getField(n);

            if (!isColumnStyle(styles[currentColumn], USE_PREFERRED_SIZE))
            { // do
                // the others we missed from above
                layoutChild(field, Math.max(0, _columnWidths[currentColumn]
                        - (field.getMarginLeft()
                        + field.getMarginRight())),
                        Math.max(0, layoutHeight
                                - y
                                - (field.getMarginBottom()
                                + field.getMarginTop())));
            }

            _rowHeights[currentRow] = Math.max(_rowHeights[currentRow], field
                     .getExtent().height
                     + field.getMarginBottom()
                     + field.getMarginTop());

            currentColumn++;
            if ((n == (numberFields - 1)) || (currentColumn >= _columns))
            {
                // we are at the end of the row or list, so now
                // go and actually do the positioning for each row
                int x = getPaddingLeft();
                for (int i = 0; i < currentColumn; i++)
                {
                    Field field1 = getField(i, currentRow);
                    XYPoint offset = calcAlignmentOffset(field1, Math.max(0,
                             _columnWidths[i]
                                    - (field1.getMarginLeft() + field1
                                            .getMarginRight())), Math.max(0,
                            _rowHeights[currentRow]
                                  - (field1.getMarginBottom()
                                  + field1.getMarginTop())));
                     setPositionChild(field1, x + offset.x
                        + field1.getMarginLeft(), y + offset.y
                        + field1.getMarginTop());
                     x += _columnWidths[i] + _horizPadding;
                }

                y += _rowHeights[currentRow];
                currentColumn = 0;
                currentRow++;
            }
        }

        int totalWidth = 0;
        if (isStyle(Field.USE_ALL_WIDTH))
        {
            totalWidth = layoutWidth;
        }
        else
        {
            for (int i = 0; i < _columns; i++)
            {
                totalWidth += _columnWidths[i]
                      + ((i < (_columns - 1)) ? _horizPadding : 0);
            }
        }

        totalWidth += getPaddingLeft() + getPaddingRight();

        y += getPaddingBottom();
        setExtent(totalWidth, Math.min(y, layoutHeight));
    }

    /**
     * Navigation movement to allow for both cell to cell within
     * a columns and row movement
     */
    protected boolean navigationMovement(int dx, int dy, int status, int time)
    {
        int focusIndex = getFieldWithFocusIndex();
        int dirY = (dy > 0) ? 1 : -1;
        int absY = Math.abs(dy);

        for (int y = 0; y < absY; y++)
        {
            focusIndex += _columns * dirY;
            if (focusIndex < 0 || focusIndex >= getFieldCount())
            {
                //#ifdef BlackBerrySDK4.5.0
                this.invalidate(); //ref #217
                //#endif
                return false;
            }
            else
            {
                Field f = getField(focusIndex);
                if (f.isFocusable())
                {
                    f.setFocus();
                }
                else
                    y--; // do it over again
            }
        }

        int dirX = (dx > 0) ? 1 : -1;
        int absX = Math.abs(dx);
        for (int x = 0; x < absX; x++)
        {
            focusIndex += dirX;
            if (focusIndex < 0 || focusIndex >= getFieldCount())
            {
                //#ifdef BlackBerrySDK4.5.0
                this.invalidate(); //Ref #217
                //#endif
                return false;
            }
            else
            {
                Field f = getField(focusIndex);
                if (f.isFocusable())
                {
                    f.setFocus();
                }
                else
                    x--; // do it over again
            }
        }
        
        //#ifdef BlackBerrySDK4.5.0
        this.invalidate(); //ref #217
        //#endif
        
        return true;
    }

    /**
     * Calculate the styles and return appropriate XY offset
     * locations within the cell.
     * @param field
     * @param width
     * @param height
     * @return
     */
    private XYPoint calcAlignmentOffset(Field field, int width, int height)
    {
        XYPoint offset = new XYPoint(0, 0);
        long fieldStyle = field.getStyle();
        long field_x_style = fieldStyle & Field.FIELD_HALIGN_MASK;

        if (field_x_style == Field.FIELD_RIGHT)
        {
            offset.x = width - field.getExtent().width;
        }
        else if (field_x_style == Field.FIELD_HCENTER)
        {
            offset.x = (width - field.getExtent().width) / 2;
        }
        long field_y_style = fieldStyle & Field.FIELD_VALIGN_MASK;
        if (field_y_style == Field.FIELD_BOTTOM)
        {
            offset.y = height - field.getExtent().height;
        }
        else if (field_y_style == Field.FIELD_VCENTER)
        {
            offset.y = (height - field.getExtent().height) / 2;
        }
        return offset;
    }
}

 

Comments
by Developer ‎03-05-2012 05:23 PM - edited ‎03-05-2012 05:24 PM

Noticed that the code in the attached .java file is an old version of the code shown in the web page.

by Developer on ‎03-05-2012 05:43 PM

I've changed the code a little bit by fixing some issues I found in it. For example, when SPLIT_REMAINING_WIDTH is used the reminder of the integer division is not consider at all, even if the manager should USE_ALL_WIDTH. I also fixed some other minor issues. An updated copy of the java code could be download here: http://bit.ly/zsO9cd

by Administrator on ‎03-06-2012 03:34 PM

Thank you for the update.  I'll review this and update the article.