The document discusses code for a dish list form and dish edit form. It summarizes:
1) The dish list form contains a grid layout to display dishes. A floating action button allows adding new dishes. Dishes are displayed with images, titles, and descriptions.
2) The dish edit form displays dish details and allows editing name, price, description, and image. It positions title/button components above the image and positions the delete button at the bottom.
3) Complex layouts are used to precisely position components like the floating action button between the title and body sections. Margin adjustments are made to align components like the button with the toolbar height.
1. Initial UI Mockup - Part III
We’ll continue with the code for the dish list
2. private static GridLayout createLayout() {
if(Display.getInstance().isTablet()) {
return new GridLayout(6, 6);
} else {
return new GridLayout(3, 2);
}
}
public DishListForm(AppSettings app) {
super(app, createLayout());
for(Dish d : Restaurant.getInstance().menu.get().dishes) {
addDish(d);
}
FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.bindFabToContainer(getContentPane());
fab.addActionListener(e -> {
Dish d = new Dish().description.set("Description of the dish...").
name.set("Dish Name").
price.set(3.0);
d.setFullSize(Resources.getGlobalResources().getImage("food1.jpg"));
Restaurant.getInstance().menu.get().dishes.add(d);
addDish(d);
revalidate();
new DishEditForm(d).show();
});
}
DishListForm
The dish list form is the form that contains the set of dishes we can select from
3. private static GridLayout createLayout() {
if(Display.getInstance().isTablet()) {
return new GridLayout(6, 6);
} else {
return new GridLayout(3, 2);
}
}
public DishListForm(AppSettings app) {
super(app, createLayout());
for(Dish d : Restaurant.getInstance().menu.get().dishes) {
addDish(d);
}
FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.bindFabToContainer(getContentPane());
fab.addActionListener(e -> {
Dish d = new Dish().description.set("Description of the dish...").
name.set("Dish Name").
price.set(3.0);
d.setFullSize(Resources.getGlobalResources().getImage("food1.jpg"));
Restaurant.getInstance().menu.get().dishes.add(d);
addDish(d);
revalidate();
new DishEditForm(d).show();
});
}
DishListForm
The elements within this form are stored in a grid layout. You will notice that the number of rows is hardcoded, that is because rows are added implicitly as we add more
elements but columns won’t change by default. This means that if we only have 1 or two elements the rest of the places will remain blank and the one element we have
won’t take up all the space.
4. private static GridLayout createLayout() {
if(Display.getInstance().isTablet()) {
return new GridLayout(6, 6);
} else {
return new GridLayout(3, 2);
}
}
public DishListForm(AppSettings app) {
super(app, createLayout());
for(Dish d : Restaurant.getInstance().menu.get().dishes) {
addDish(d);
}
FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.bindFabToContainer(getContentPane());
fab.addActionListener(e -> {
Dish d = new Dish().description.set("Description of the dish...").
name.set("Dish Name").
price.set(3.0);
d.setFullSize(Resources.getGlobalResources().getImage("food1.jpg"));
Restaurant.getInstance().menu.get().dishes.add(d);
addDish(d);
revalidate();
new DishEditForm(d).show();
});
}
DishListForm
The floating action button at the bottom allows us to add a new dish to the set of existing dishes. Notice that we instantly add a dish and show the edit UI forcing the
user to delete the dish if he wants to cancel this addition. That’s a common practice on mobile where dealing with the OK/Cancel process is sometimes harder than just
doing something like this.
5. final void addDish(Dish d) {
ScaleImageButton sb = new ScaleImageButton(d.getFullSize());
sb.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
Label title = new Label(d.name.get(), "DishName");
Label description = new Label(d.description.get(), "DishDescription");
Container titleAndDescription = BoxLayout.encloseY(title, description);
titleAndDescription.setUIID("BlackGradient");
Container cnt = LayeredLayout.encloseIn(sb,
BorderLayout.south(titleAndDescription));
add(cnt);
cnt.setLeadComponent(sb);
setLayout(createLayout());
sb.addActionListener(e -> new DishEditForm(d).show());
}
DishListForm
The add dish method adds a UI element to the list of dishes. That element includes a black gradient overlay which makes the white text on top readable even with a
white image below.
6. final void addDish(Dish d) {
ScaleImageButton sb = new ScaleImageButton(d.getFullSize());
sb.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
Label title = new Label(d.name.get(), "DishName");
Label description = new Label(d.description.get(), "DishDescription");
Container titleAndDescription = BoxLayout.encloseY(title, description);
titleAndDescription.setUIID("BlackGradient");
Container cnt = LayeredLayout.encloseIn(sb,
BorderLayout.south(titleAndDescription));
add(cnt);
cnt.setLeadComponent(sb);
setLayout(createLayout());
sb.addActionListener(e -> new DishEditForm(d).show());
}
DishListForm
The entire component is a lead component which means all events within the container are delegated to the scale image button. That means that the listener on the scale
image button in the last line will receive all of the events in the container hierachy
7. super(d.name.get(), new BorderLayout());
Toolbar tb = getToolbar();
Button back = new Button("", "Title");
Button ok = new Button("", "Title");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK, 5);
FontImage.setMaterialIcon(ok, FontImage.MATERIAL_CHECK, 5);
TextField title = new TextField(d.name.get());
title.setUIID("Title");
tb.setTitleComponent(
BorderLayout.centerEastWest(
BoxLayout.encloseY(title),
FlowLayout.encloseRight(ok),
FlowLayout.encloseIn(back)));
tb.setUIID("BlueGradient");
ScaleImageLabel backgroundImage = new ScaleImageLabel(d.getFullSize()) {
@Override
protected Dimension calcPreferredSize() {
Dimension d = super.calcPreferredSize();
d.setHeight(Math.min(d.getHeight(), Display.getInstance().convertToPixels(38)));
return d;
}
};
DishEditForm
We show the dish edit form when editing a dish, since a dish is instantly created and edited there is no form for new dish and this form doesn’t have 2 modes like you
normally would for edit or create. In this app there is only one mode: edit.
8. super(d.name.get(), new BorderLayout());
Toolbar tb = getToolbar();
Button back = new Button("", "Title");
Button ok = new Button("", "Title");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK, 5);
FontImage.setMaterialIcon(ok, FontImage.MATERIAL_CHECK, 5);
TextField title = new TextField(d.name.get());
title.setUIID("Title");
tb.setTitleComponent(
BorderLayout.centerEastWest(
BoxLayout.encloseY(title),
FlowLayout.encloseRight(ok),
FlowLayout.encloseIn(back)));
tb.setUIID("BlueGradient");
ScaleImageLabel backgroundImage = new ScaleImageLabel(d.getFullSize()) {
@Override
protected Dimension calcPreferredSize() {
Dimension d = super.calcPreferredSize();
d.setHeight(Math.min(d.getHeight(), Display.getInstance().convertToPixels(38)));
return d;
}
};
DishEditForm
The title area of the dish edit form is built of components because it is large and relatively custom I preferred to handle all of the commands and functionality in code. So
the title itself is a text field again just like in the main form as the dish title can be edited “in place”.
The back and ok buttons are just standard buttons with the right UIID and icon.
9. super(d.name.get(), new BorderLayout());
Toolbar tb = getToolbar();
Button back = new Button("", "Title");
Button ok = new Button("", "Title");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK, 5);
FontImage.setMaterialIcon(ok, FontImage.MATERIAL_CHECK, 5);
TextField title = new TextField(d.name.get());
title.setUIID("Title");
tb.setTitleComponent(
BorderLayout.centerEastWest(
BoxLayout.encloseY(title),
FlowLayout.encloseRight(ok),
FlowLayout.encloseIn(back)));
tb.setUIID("BlueGradient");
ScaleImageLabel backgroundImage = new ScaleImageLabel(d.getFullSize()) {
@Override
protected Dimension calcPreferredSize() {
Dimension d = super.calcPreferredSize();
d.setHeight(Math.min(d.getHeight(), Display.getInstance().convertToPixels(38)));
return d;
}
};
DishEditForm
This is where it get’s interesting… The title component is now really tall because of the background image. So if I had used a standard add material command it would
have been placed somewhere along the middle of the layout instead of at the top.
So to get around this I created a border layout which effectively occupies this area but again if I’d add the components directly they will be placed at the middle vertically
instead of at the top as I would like. So I wrapped the two commands with a flow layout to push them to the top…
I wrapped the text field with a box layout though and the reason is a bit more complicated. Flow layout doesn’t deal well with resizable components like text field since it
tries to break a line when we run out of space. If the title is too long it will try to break a line instead of letting the text field overflow which is what I want. Box layout
doesn’t have that problem.
10. backgroundImage.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
add(BorderLayout.NORTH, backgroundImage);
FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_COLLECTIONS);
Style fabStyle = fab.getAllStyles();
fab.bindFabToContainer(getContentPane(), RIGHT, TOP);
final Form previous = Display.getInstance().getCurrent();
Component.setSameHeight(tb, backgroundImage);
fabStyle.setMarginUnit(Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS,
Style.UNIT_TYPE_DIPS);
fabStyle.setMarginTop(tb.getPreferredH() - fab.getPreferredH() / 2);
Button delete = new Button("Delete Dish", "DeleteButton");
FontImage.setMaterialIcon(delete, FontImage.MATERIAL_DELETE);
delete.addActionListener(e -> previous.showBack());
ok.addActionListener(e -> previous.showBack());
back.addActionListener(e -> previous.showBack());
add(BorderLayout.SOUTH, delete);
TextField price = new TextField("" + d.price.get(), "Price", 5, TextField.DECIMAL);
TextField description = new TextField(d.description.get(), "Description", 5, TextField.ANY);
description.setSingleLineTextArea(false);
description.setRows(4);
add(BorderLayout.CENTER, BoxLayout.encloseY(
new Label("Price", "TextFieldLabel"),
price,
new Label("Description", "TextFieldLabel"),
description
));
DishEditForm
This code right here positions the floating action button on the border between the title area and the body. Notice that this component is positioned on top of both and
doesn’t require a special empty region like the icon in the previous form. We bind the floating action button directly to the layered layout on the top right which should
position it really high in the form but we then use this code to set the margin of the floating action button so it will match the toolbar height minus half the height of the
floating button. This positions it perfectly.
11. backgroundImage.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
add(BorderLayout.NORTH, backgroundImage);
FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_COLLECTIONS);
Style fabStyle = fab.getAllStyles();
fab.bindFabToContainer(getContentPane(), RIGHT, TOP);
final Form previous = Display.getInstance().getCurrent();
Component.setSameHeight(tb, backgroundImage);
fabStyle.setMarginUnit(Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS,
Style.UNIT_TYPE_DIPS);
fabStyle.setMarginTop(tb.getPreferredH() - fab.getPreferredH() / 2);
Button delete = new Button("Delete Dish", "DeleteButton");
FontImage.setMaterialIcon(delete, FontImage.MATERIAL_DELETE);
delete.addActionListener(e -> previous.showBack());
ok.addActionListener(e -> previous.showBack());
back.addActionListener(e -> previous.showBack());
add(BorderLayout.SOUTH, delete);
TextField price = new TextField("" + d.price.get(), "Price", 5, TextField.DECIMAL);
TextField description = new TextField(d.description.get(), "Description", 5, TextField.ANY);
description.setSingleLineTextArea(false);
description.setRows(4);
add(BorderLayout.CENTER, BoxLayout.encloseY(
new Label("Price", "TextFieldLabel"),
price,
new Label("Description", "TextFieldLabel"),
description
));
DishEditForm
Last but not least we can see the component representing the delete button at the bottom followed by stub action handling code for the various buttons.
Delete is placed in the south so it will always appear at the very bottom of the form regardless of scrolling