Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Creating an Uber Clone - Part IX - Transcript.pdf
1. Creating an Uber Clone - Part IX
The “Where to?” UI is a pretty big piece of the puzzle and it also includes the navigation UI which is the bottom portion
5. private void showToNavigationBar(Container parentLayer) {
MultiButton addHome = new MultiButton("Add Home");
addHome.setUIID("Container");
addHome.setUIIDLine1("WhereToButtonLine1");
addHome.setIconUIID("WhereToButtonIcon");
FontImage.setMaterialIcon(addHome, FontImage.MATERIAL_HOME);
MultiButton addWork = new MultiButton("Add Work");
addWork.setUIID("Container");
addWork.setUIIDLine1("WhereToButtonLine1");
addWork.setIconUIID("WhereToButtonIcon");
FontImage.setMaterialIcon(addWork, FontImage.MATERIAL_WORK);
MultiButton savedPlaces = new MultiButton("Saved Places");
savedPlaces.setUIID("Container");
savedPlaces.setUIIDLine1("WhereToButtonLineNoBorder");
savedPlaces.setEmblemUIID("WhereToButtonLineNoBorder");
savedPlaces.setIconUIID("WhereToButtonIcon");
savedPlaces.setEmblem(FontImage.createMaterial(
FontImage.MATERIAL_NAVIGATE_NEXT,
savedPlaces.getIconComponent().getUnselectedStyle()));
FontImage.setMaterialIcon(savedPlaces,
FontImage.MATERIAL_STAR_BORDER);
Destination UI
Let’s jump right in to the “showNavigationBar” method.
The top elements are relatively simple multi-buttons, we use Container as their UIID so they will be transparent with 0 padding/margin.
6. savedPlaces.setEmblem(FontImage.createMaterial(
FontImage.MATERIAL_NAVIGATE_NEXT,
savedPlaces.getIconComponent().getUnselectedStyle()));
FontImage.setMaterialIcon(savedPlaces,
FontImage.MATERIAL_STAR_BORDER);
Label whereSeparator = new Label("", "WhereSeparator");
whereSeparator.setShowEvenIfBlank(true);
MultiButton history1 = new MultiButton("Mikve Yisrael Str...");
history1.setUIID("Container");
history1.setUIIDLine1("WhereToButtonLine1");
history1.setIconUIID("WhereToButtonIcon");
FontImage.setMaterialIcon(history1, FontImage.MATERIAL_HISTORY);
Container result = BoxLayout.encloseY(addHome, addWork,
savedPlaces, whereSeparator, history1);
result.setUIID("Form");
result.setScrollableY(true);
result.setScrollVisible(false);
result.setY(getDisplayHeight());
Destination UI
The separator is just a label with a specific style. Notice that blank labels/buttons etc. are hidden by default in Codename One and you should invoke
setShowEvenIfBlank if you want such a label to still render
9. Label whereSeparator = new Label("", "WhereSeparator");
whereSeparator.setShowEvenIfBlank(true);
MultiButton history1 = new MultiButton("Mikve Yisrael Str...");
history1.setUIID("Container");
history1.setUIIDLine1("WhereToButtonLine1");
history1.setIconUIID("WhereToButtonIcon");
FontImage.setMaterialIcon(history1, FontImage.MATERIAL_HISTORY);
Container result = BoxLayout.encloseY(addHome, addWork,
savedPlaces, whereSeparator, history1);
result.setUIID("Form");
result.setScrollableY(true);
result.setScrollVisible(false);
result.setY(getDisplayHeight());
result.setWidth(getDisplayWidth());
result.setHeight(result.getPreferredH());
parentLayer.add(CENTER, result);
parentLayer.animateLayout(200);
}
Destination UI
The showing of this element is animated from the bottom of the Form
22. from.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(square);
if(layer.getComponentCount() > 1) {
Component c = layer.getComponentAt(1);
c.setY(getDisplayHeight());
layer.animateUnlayout(200, 150, () -> {
c.remove();
revalidate();
});
}
}
public void focusLost(Component cmp) {
fromSelected.setIcon(circle);
}
});
to.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(circle);
toSelected.setIcon(square);
showToNavigationBar(layer);
}
public void focusLost(Component cmp) {
toSelected.setIcon(circle);
}
});
Focus Listener
Now that we added this we need to show this UI and hide it when the user toggles the focus in the text fields. We can do this by binding focus listeners to the to and
from text fields.
When focus is lost/gained we toggle between the square and circle modes by setting the icon to the appropriate labels
23. from.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(square);
if(layer.getComponentCount() > 1) {
Component c = layer.getComponentAt(1);
c.setY(getDisplayHeight());
layer.animateUnlayout(200, 150, () -> {
c.remove();
revalidate();
});
}
}
public void focusLost(Component cmp) {
fromSelected.setIcon(circle);
}
});
to.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(circle);
toSelected.setIcon(square);
showToNavigationBar(layer);
}
public void focusLost(Component cmp) {
toSelected.setIcon(circle);
}
});
Focus Listener
We always have one container in the layer except for the case where the second component is the where to container. It's always the second component because it's
always added last.
24. from.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(square);
if(layer.getComponentCount() > 1) {
Component c = layer.getComponentAt(1);
c.setY(getDisplayHeight());
layer.animateUnlayout(200, 150, () -> {
c.remove();
revalidate();
});
}
}
public void focusLost(Component cmp) {
fromSelected.setIcon(circle);
}
});
to.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(circle);
toSelected.setIcon(square);
showToNavigationBar(layer);
}
public void focusLost(Component cmp) {
toSelected.setIcon(circle);
}
});
Focus Listener
We set the position of this container below the forms
25. from.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(square);
if(layer.getComponentCount() > 1) {
Component c = layer.getComponentAt(1);
c.setY(getDisplayHeight());
layer.animateUnlayout(200, 150, () -> {
c.remove();
revalidate();
});
}
}
public void focusLost(Component cmp) {
fromSelected.setIcon(circle);
}
});
to.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(circle);
toSelected.setIcon(square);
showToNavigationBar(layer);
}
public void focusLost(Component cmp) {
toSelected.setIcon(circle);
}
});
Focus Listener
Animate "unlayout" moves the component outside of the screen to the position we asked for using a smooth animation
26. from.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(square);
if(layer.getComponentCount() > 1) {
Component c = layer.getComponentAt(1);
c.setY(getDisplayHeight());
layer.animateUnlayout(200, 150, () -> {
c.remove();
revalidate();
});
}
}
public void focusLost(Component cmp) {
fromSelected.setIcon(circle);
}
});
to.addFocusListener(new FocusListener() {
public void focusGained(Component cmp) {
fromSelected.setIcon(circle);
toSelected.setIcon(square);
showToNavigationBar(layer);
}
public void focusLost(Component cmp) {
toSelected.setIcon(circle);
}
});
Focus Listener
This callback is invoked when the unlayout completes. At this point we have an invalid UI that needs a layout but before we do that we remove the component that we
animated out of the form
27. back.addActionListener(e -> {
navigationToolbar.setY(-navigationToolbar.getHeight());
if(layer.getComponentCount() > 1) {
layer.getComponentAt(1).setY(getDisplayHeight());
}
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
layer.removeAll();
revalidate();
});
});
Back
Now that the UI appears we also need to remove it when going back so I'll update the back action listener from above to handle the "Where to?" UI as well.
This is the exact same unlayout operation we did before
28. navigationToolbar.getUnselectedStyle().setBgPainter((g1, rect) -> {
g1.setAlpha(255);
g1.setColor(0xffffff);
if(dropShadow != null) {
if(layer.getComponentCount() > 1) {
g1.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
g1.drawImage(dropShadow, rect.getX(), rect.getY() +
rect.getHeight() - dropShadow.getHeight(), rect.getWidth(),
dropShadow.getHeight());
g1.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() -
dropShadow.getHeight() / 2);
} else {
g1.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
g1.setColor(0xa4a4ac);
g1.setAntiAliased(true);
int x = fromSelected.getAbsoluteX() + fromSelected.getWidth() / 2 - 1;
int y = fromSelected.getAbsoluteY() + fromSelected.getHeight() / 2 +
circle.getHeight() / 2;
g1.fillRect(x, y, 2, toSelected.getAbsoluteY() - y +
toSelected.getHeight() / 2 - circle.getHeight() / 2);
});
Background Painter
And finally we need to make a subtle but important change to the background painter code from before.
Because of the drop shadow a gap is formed between the top and bottom pieces so a special case here paints a white rectangle under the shadow to hide the gap.
Without that the shadow would appear on top of the map and not on top of a white background.
Once this is done opening the "Where To?" UI and toggling the fields should work as expected.