Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
create-netflix-clone-06-client-ui_transcript.pdf
1. Creating a Netflix Clone
VI
For this final part we’ll cover the UI of the client which is relatively simple. We don’t have that many forms but to be fair the basic Netflix app doesn’t have too many forms
either so this isn’t too different from the original.
The one special thing we do here is use the layered toolbar for the UI which I will discuss soon enough
CSS is used for the design, I’ll introduce the applicable CSS incrementally when introducing a specific UIID
2. codenameone.com github.com/codenameone/CodenameOne
Client UI
For this final part we’ll cover the UI of the client which is relatively simple. We don’t have that many forms but to be fair the basic Netflix app doesn’t have too many forms
either so this isn’t too different from the original.
The one special thing we do here is use the layered toolbar for the UI which I will discuss soon enough
CSS is used for the design, I’ll introduce the applicable CSS incrementally when introducing a specific UIID
3. codenameone.com github.com/codenameone/CodenameOne
Client UI
There are just three forms in the demo
We’re using layered toolbar for the UI
CSS is used for the UI design
For this final part we’ll cover the UI of the client which is relatively simple. We don’t have that many forms but to be fair the basic Netflix app doesn’t have too many forms
either so this isn’t too different from the original.
The one special thing we do here is use the layered toolbar for the UI which I will discuss soon enough
CSS is used for the design, I’ll introduce the applicable CSS incrementally when introducing a specific UIID
4. #Constants {
includeNativeBool: true;
labelGap: 1;
}
Form {
background: black;
}
TitleCommand {
color: white;
}
ToolbarGradient {
border: none;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.6));
padding: 2mm;
}
ThumbIcon {
background: transparent;
margin: 0px;
padding: 8mm;
padding-right: 3mm;
}
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
I won’t go over the entire CSS and will instead go back to it when introducing specific UIIDs. I will cover the generic concepts first though.
We have only two constants in the CSS. The first is the include native feature which should be on by default always. The second is a standard label gap between the label
and an icon. I think 1 millimeter is generally a good number here.
5. public class BaseForm extends Form {
public BaseForm(Layout l) {
super(l);
}
@Override
protected void initGlobalToolbar() {
Toolbar tb = new Toolbar(true);
setToolbar(tb);
tb.setUIID("ToolbarGradient");
}
}
Source Listing - BaseForm
codenameone.com github.com/codenameone/CodenameOne
We don’t have too much in common between the forms at this level of the UI so there’s only one method in common.
The initGlobalToolbar method initializes the toolbar component when global toolbar is turned on (which is the default). We do two things here. We set the toolbar to use
the layered mode. We do that by passing true to the toolbar constructor.
Next we set the UIID of the Toolbar to ToolbarGradient which we use to indicate the translucent gradient background to separate the toolbar from the content
6. }
Form {
background: black;
}
TitleCommand {
color: white;
}
ToolbarGradient {
border: none;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.6));
padding: 2mm;
}
ThumbIcon {
background: transparent;
margin: 0px;
padding: 8mm;
padding-right: 3mm;
}
HeroImageLogo {
/* margin: 35% 22% 5mm 22%; */
margin: 35mm 7mm 5mm 7mm;
padding: 3.5mm;
background: transparent;
}
HeroImagePlayButton {
border: none;
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The toolbar gradient is a gradient in black between 0.6 alpha to almost clear alpha. This creates a slight fade effect over the title area so the title will still be visible if the
image in the background has the same colors.
Codename One doesn’t currently support alpha gradients. As a solution the CSS support generates an image of the gradient during build and uses that
7. public class SplashForm extends Form {
private SplashForm() {
super(new BorderLayout(BorderLayout.CENTER_BEHAVIOR_CENTER));
}
private void init() {
Resources r = Resources.getGlobalResources();
add(CENTER, new Label(r.getImage("netflix-logo.png")));
}
public static SplashForm create() {
SplashForm h = new SplashForm();
h.init();
return h;
}
}
Source Listing - SplashForm
codenameone.com github.com/codenameone/CodenameOne
The splash form is stupid simple. We just place the logo in the center of the form and initialize. There’s only one thing to discuss here and that’s the source of the netflix-
logo.png file.
8. font-size: 2.1mm;
border: none;
background-color: black;
padding: 1.5mm 0.5mm 1.5mm 0.5mm;
margin: 0px;
}
/* =============== IMAGES ============= */
ImageImports1 {
background-image: url(images/data/show-logo.png),
url(images/data/hero-background.jpg),
url(images/data/thumb1.jpg),
url(images/data/thumb2.jpg),
url(images/data/thumb3.jpg),
url(images/data/thumb4.jpg),
url(images/data/thumb5.jpg),
url(images/data/thumb6.jpg),
url(images/data/thumb7.jpg),
url(images/data/thumb8.jpg);
cn1-source-dpi: 0;
}
ImageImports2 {
background-image: url(images/netflix-logo.png);
cn1-source-dpi: 320;
}
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
All the images are declared in the CSS under two dummy UIIDs specifically ImageImports1 and ImageImports2. This just pulls all the images into the resource file so we
can later make use of them.
9. font-size: 2.1mm;
border: none;
background-color: black;
padding: 1.5mm 0.5mm 1.5mm 0.5mm;
margin: 0px;
}
/* =============== IMAGES ============= */
ImageImports1 {
background-image: url(images/data/show-logo.png),
url(images/data/hero-background.jpg),
url(images/data/thumb1.jpg),
url(images/data/thumb2.jpg),
url(images/data/thumb3.jpg),
url(images/data/thumb4.jpg),
url(images/data/thumb5.jpg),
url(images/data/thumb6.jpg),
url(images/data/thumb7.jpg),
url(images/data/thumb8.jpg);
cn1-source-dpi: 0;
}
ImageImports2 {
background-image: url(images/netflix-logo.png);
cn1-source-dpi: 320;
}
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The source DPI is the reason we have to image import UIIDs. When source DPI is set to 0 it means we want the image imported “as is”. Not as a multi-image but rather
as a single image.
10. font-size: 2.1mm;
border: none;
background-color: black;
padding: 1.5mm 0.5mm 1.5mm 0.5mm;
margin: 0px;
}
/* =============== IMAGES ============= */
ImageImports1 {
background-image: url(images/data/show-logo.png),
url(images/data/hero-background.jpg),
url(images/data/thumb1.jpg),
url(images/data/thumb2.jpg),
url(images/data/thumb3.jpg),
url(images/data/thumb4.jpg),
url(images/data/thumb5.jpg),
url(images/data/thumb6.jpg),
url(images/data/thumb7.jpg),
url(images/data/thumb8.jpg);
cn1-source-dpi: 0;
}
ImageImports2 {
background-image: url(images/netflix-logo.png);
cn1-source-dpi: 320;
}
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The logo is the only image where we’re interested in multi-image behavior. Since most images come from the server we don’t need many multi-images in the app.
11. public class HomeForm extends BaseForm {
private HomeForm() {
super(new BorderLayout());
}
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The HomeForm is the main UI of the application listing the content that the user can select from. It’s a base form which means the title of the form is overlaid on the
content and it uses border layout.
12. add(CENTER, tabs);
}
private Container movieList(List<Content> lst) {
Container p = new Container(BoxLayout.x());
for(Content c : lst) {
ScaleImageButton b = new ScaleImageButton(c.icon.get());
b.setUIID("ThumbIcon");
p.add(b);
b.addActionListener(e -> {
DetailsForm.create(c).show();
});
}
p.setScrollableX(true);
return p;
}
public static HomeForm create(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
HomeForm h = new HomeForm();
h.init(lead, popularTitles, myTitles, recommended);
return h;
}
}
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
I’ll skip to the bottom first. This class is created using a create method that returns the class instance. It accepts the content information as the argument to build the UI.
The actual implementation of the layout is in the init method we see above.
13. public class HomeForm extends BaseForm {
private HomeForm() {
super(new BorderLayout());
}
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The init method creates the entire UI. It start with the logo title we can see here. That matches the same image we see in the splash screen. That’s mostly laziness on my
part but isn’t too far off from the actual Netflix UI
14. public class HomeForm extends BaseForm {
private HomeForm() {
super(new BorderLayout());
}
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
By adding a command to the side menu the hamburger menu appears automatically. I didn’t want to go into the design and implementation of the side menu so I left this
effectively blank.
15. public class HomeForm extends BaseForm {
private HomeForm() {
super(new BorderLayout());
}
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
I also added a search command which is again blank since I didn’t implement that. Technically I just used that for the icon. I could have just used
addMaterialCommandToRightBar but that would have required a slightly longer line of code so I chose this approach
16. }
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The main UI has a logo image here which is different from the background hero shot.
Now you might be thinking: why not have the logo as a part of the background hero shot? Why do we need a separate image for the logo?
Two reasons:
- We want the logo to appear above the play button exactly. If it’s a part of the background image we won’t be able to tell where that is.
- We want the ability to scale the background and foreground image differently. In the background we want a scale to fill so the UI will look good in all resolutions. For the
foreground we want a scale to fit behavior so the logo text will always be visible regardless of the device resolution.
17. }
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
We set the UIID for the series logo. This impacts the following CSS.
18. ToolbarGradient {
border: none;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.6));
padding: 2mm;
}
ThumbIcon {
background: transparent;
margin: 0px;
padding: 8mm;
padding-right: 3mm;
}
HeroImageLogo {
margin: 35mm 7mm 5mm 7mm;
padding: 3.5mm;
background: transparent;
}
HeroImagePlayButton {
border: none;
border-radius: 1.5mm;
background-color: #D8D8D8;
color: black;
font-family: "native:MainRegular";
font-size: 3mm;
padding: 2mm 4mm 2mm 4mm;
}
Lead {
color: white;
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The margin and padding push the logo to the right location in the middle with the right amount of spacing and the background is defined as transparent so the
background image will be visible through the logo.
19. }
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The play button looks like this. Again most of the work is done in the CSS for the button
20. background: transparent;
margin: 0px;
padding: 8mm;
padding-right: 3mm;
}
HeroImageLogo {
margin: 35mm 7mm 5mm 7mm;
padding: 3.5mm;
background: transparent;
}
HeroImagePlayButton {
border: none;
border-radius: 1.5mm;
background-color: #D8D8D8;
color: black;
font-family: "native:MainRegular";
font-size: 3mm;
padding: 2mm 4mm 2mm 4mm;
}
Lead {
color: white;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.01));
padding: 5mm 2mm 0.3mm 2mm;
font-family: "native:MainRegular";
font-size: 2.4mm;
}
PlayNowButton {
color: white;
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
We use a 1.5mm round border with a gray background and black foreground for the text/icons
21. }
private void init(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
Resources r = Resources.getGlobalResources();
Toolbar tb = getToolbar();
((Label)tb.getTitleComponent()).setIcon(r.getImage("netflix-logo.png"));
// adding this just so the sidemenu and search icon appear
tb.addMaterialCommandToLeftSideMenu("Placeholder", MATERIAL_AIRPLANEMODE_ON, e ->{});
tb.addSearchCommand(e -> {});
ScaleImageLabel logo = new ScaleImageLabel(lead.logo.get());
logo.setUIID("HeroImageLogo");
Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The background image comes dynamically from the server so we can’t set it from CSS. We create a box layout with the logo, play button and the “Popular on Netflix”
label. We then set the background image dynamically using the style object.
22. }
HeroImagePlayButton {
border: none;
border-radius: 1.5mm;
background-color: #D8D8D8;
color: black;
font-family: "native:MainRegular";
font-size: 3mm;
padding: 2mm 4mm 2mm 4mm;
}
Lead {
color: white;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.01));
padding: 5mm 2mm 0.3mm 2mm;
font-family: "native:MainRegular";
font-size: 2.4mm;
}
PlayNowButton {
color: white;
background-color: rgba(0, 0, 0, 0.6);
border: 2px #ffffff cn1-round-border;
padding: 2mm;
font-family: "native:MainRegular";
font-size: 3.5mm;
margin: 15mm 7mm 15mm 7mm;
}
ShowTitleOverlay {
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The Lead UIID is a special case with a dark gradient background. It’s overlaid on the title image and needs that gradient to be visible on all image backgrounds
23. Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
new Label("Recommended", "Lead"),
movieList(recommended));
content.setScrollableY(true);
tabs.addTab("Home", MATERIAL_HOME, 4, content);
tabs.addTab("Search", MATERIAL_SEARCH, 4, new Label("TODO"));
tabs.addTab("Coming Soon", MATERIAL_ONDEMAND_VIDEO, 4, new Label("TODO"));
tabs.addTab("More", MATERIAL_MENU, 4, new Label("TODO"));
add(CENTER, tabs);
}
private Container movieList(List<Content> lst) {
Container p = new Container(BoxLayout.x());
for(Content c : lst) {
ScaleImageButton b = new ScaleImageButton(c.icon.get());
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
The tabs are set to appear at the bottom explicitly to avoid top Android style tabs. I could have defined this in a theme constant but chose to do it in the code in this
case.
24. padding: 3mm;
margin: 0px;
}
DescriptionFormButton {
color: white;
font-family: "native:MainLight";
font-size: 3.5mm;
padding: 3mm;
margin: 0px;
}
Tab {
color: white;
font-family: "native:MainLight";
font-size: 2.1mm;
border: none;
background-color: black;
padding: 1.5mm 0.5mm 1.5mm 0.5mm;
margin: 0px;
}
/* =============== IMAGES ============= */
ImageImports1 {
background-image: url(images/data/show-logo.png),
url(images/data/hero-background.jpg),
url(images/data/thumb1.jpg),
url(images/data/thumb2.jpg),
url(images/data/thumb3.jpg),
url(images/data/thumb4.jpg),
Source Listing - CSS
codenameone.com github.com/codenameone/CodenameOne
The Lead UIID is a special case with a dark gradient background. It’s overlaid on the title image and needs that gradient to be visible on all image backgrounds
25. Button play = new Button("PLAY", MATERIAL_PLAY_ARROW, "HeroImagePlayButton");
Container heroTitle = BoxLayout.encloseY(logo,
FlowLayout.encloseCenter(play),
new Label("Popular on Netflix", "Lead"));
Style stl = heroTitle.getAllStyles();
stl.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stl.setBgImage(lead.heroImage.get());
Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
new Label("Recommended", "Lead"),
movieList(recommended));
content.setScrollableY(true);
tabs.addTab("Home", MATERIAL_HOME, 4, content);
tabs.addTab("Search", MATERIAL_SEARCH, 4, new Label("TODO"));
tabs.addTab("Coming Soon", MATERIAL_ONDEMAND_VIDEO, 4, new Label("TODO"));
tabs.addTab("More", MATERIAL_MENU, 4, new Label("TODO"));
add(CENTER, tabs);
}
private Container movieList(List<Content> lst) {
Container p = new Container(BoxLayout.x());
for(Content c : lst) {
ScaleImageButton b = new ScaleImageButton(c.icon.get());
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
Each list below is created via the movieList method. They have a lead label top and reside within a scrollable Container so we can scroll through them.
26. tabs.addTab("Home", MATERIAL_HOME, 4, content);
tabs.addTab("Search", MATERIAL_SEARCH, 4, new Label("TODO"));
tabs.addTab("Coming Soon", MATERIAL_ONDEMAND_VIDEO, 4, new Label("TODO"));
tabs.addTab("More", MATERIAL_MENU, 4, new Label("TODO"));
add(CENTER, tabs);
}
private Container movieList(List<Content> lst) {
Container p = new Container(BoxLayout.x());
for(Content c : lst) {
ScaleImageButton b = new ScaleImageButton(c.icon.get());
b.setUIID("ThumbIcon");
p.add(b);
b.addActionListener(e -> {
DetailsForm.create(c).show();
});
}
p.setScrollableX(true);
return p;
}
public static HomeForm create(Content lead, List<Content> popularTitles,
List<Content> myTitles,
List<Content> recommended) {
HomeForm h = new HomeForm();
h.init(lead, popularTitles, myTitles, recommended);
return h;
}
}
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
Let’s look at the movie list method. Here I create a box X container that’s scrollable on the X axis. Every element is a ScalableImageButton that uses the ThumbIcon UIID.
When pressed we show the DetailsForm.
27. Tabs tabs = new Tabs();
tabs.setTabPlacement(BOTTOM);
Container content = BoxLayout.encloseY(heroTitle,
movieList(popularTitles),
new Label("My List", "Lead"),
movieList(myTitles),
new Label("Recommended", "Lead"),
movieList(recommended));
content.setScrollableY(true);
tabs.addTab("Home", MATERIAL_HOME, 4, content);
tabs.addTab("Search", MATERIAL_SEARCH, 4, new Label("TODO"));
tabs.addTab("Coming Soon", MATERIAL_ONDEMAND_VIDEO, 4, new Label("TODO"));
tabs.addTab("More", MATERIAL_MENU, 4, new Label("TODO"));
add(CENTER, tabs);
}
private Container movieList(List<Content> lst) {
Container p = new Container(BoxLayout.x());
for(Content c : lst) {
ScaleImageButton b = new ScaleImageButton(c.icon.get());
b.setUIID("ThumbIcon");
p.add(b);
b.addActionListener(e -> {
DetailsForm.create(c).show();
});
}
p.setScrollableX(true);
return p;
Source Listing - HomeForm
codenameone.com github.com/codenameone/CodenameOne
Finally the tabs themselves are added to the bottom of the form.