SlideShare a Scribd company logo
Creating an Uber Clone - Part IV
In this part we’ll refine the login form and go into low level graphics for animations and background.
The Login Form
© Codename One 2017 all rights reserved
This is how I finished the previous part. I discussed the fonts but there are other differences. Obviously the flag is slightly different since I used our own resource file. But
there are two other noticeable differences. The first is the drop shadow behind the logo which is missing. The second is the background rotation of the pattern which is
iOS specific in the native app but I don’t see a reason for that. I’d like to have it on Android too…
final Image shadow = squareShadow(
squareLogo.getPreferredW(),
squareLogo.getPreferredH(),
convertToPixels(14),
0.35f);
Drop Shadow
The simplest thing to do is generate a square image of the logo that already has a translucent shadow within. This would be pretty trivial to anyone versed in photoshop
and would look great on the device...

However, my goal is to teach programming not photoshop so I'm picking the "hard way" of solving this.

The Effects class in Codename One allows us to create a shadow image for the given dimensions or image. Since the logo is square we can just use the dimensions
approach. The method accepts the size of the shadow, the blur radius which means how far it should go out of the size limits and the opacity as a value between 0 and
1.
Container logo = LayeredLayout.encloseIn(
BorderLayout.centerAbsolute(
new Label(shadow, "Container")),
BorderLayout.centerAbsolute(squareLogo)
);
Drop Shadow
So now we have an image of the shadow but the logo image and background are already fixed so we need something new. Instead of using the logo as is we place the
shadow in a layer below using the LayeredLayout and this will produce the desired effect with one HUGE caveat. It's really slow!

Shadows are computationally slow… We use gaussian blur to generate shadows and that's a very slow algorithm.
Label placeholder = new Label();
Container logo = LayeredLayout.encloseIn(
placeholder,
BorderLayout.centerAbsolute(squareLogo)
);
startThread(() -> {
Image shadow = squareShadow(squareLogo.getPreferredW(),
squareLogo.getPreferredH(),
convertToPixels(14), 0.35f);
callSerially(() -> {
logo.replace(placeholder, BorderLayout.centerAbsolute(
new Label(shadow, "Container")), null);
revalidate();
});
}, "Shadow Maker").start();
Drop Shadow
The solution is to move that code offline. The UI will appear and the shadow will appear a second later when it's ready.

The placeholder is there so we can put the shadow into place when it's ready.
Label placeholder = new Label();
Container logo = LayeredLayout.encloseIn(
placeholder,
BorderLayout.centerAbsolute(squareLogo)
);
startThread(() -> {
Image shadow = squareShadow(squareLogo.getPreferredW(),
squareLogo.getPreferredH(),
convertToPixels(14), 0.35f);
callSerially(() -> {
logo.replace(placeholder, BorderLayout.centerAbsolute(
new Label(shadow, "Container")), null);
revalidate();
});
}, "Shadow Maker").start();
Drop Shadow
When the shadow image is ready we replace it on the EDT with the the new shadow label. The label uses the Container UIID which is always transparent with 0 padding
and 0 margin.
Background
© Codename One 2017 all rights reserved
The Android version of Uber doesn't include the rotation animation for reasons that are just unclear to me. I think it might collide with some of the material design
transitions or some other problem. It works nicely on all OS's with the way I implemented it.

I could just rotate the tiles like the one pictured above and call it a day, the effect would look decent and perform well. However, I wanted better control and in order to
get that I need shapes...

Shapes allow us to draw arbitrary vectors/curves in a performant way. Since this is effectively a vector API, rotation & scaling don't distort the result. In order to use this
API I need to use the low level graphics API and the background painter.
logo.getUnselectedStyle().setBgPainter(
new LoginFormPainter(logo));
class LoginFormPainter implements Painter, Animation {
//....
}
Background Painter
We can set the painter for the logo object using this code. Notice that normally we don't need a reference to the parent component logo but in this case we need it for the
animation. I’ll go into that soon. But first I’d like to say a few words about painters… 

Styling can only go so far. If you want to customize the background of a component in a completely custom way you can use the Painter API to define the actual
rendering of the background. This overrides all style rendering and provides you with a Graphics object you can use for drawing.

Notice that the Graphics API is a low level API and might have platform specific behaviors that aren't as refined as the component/style API's. It's harder to optimize low
level graphics code so use it with caution.
class LoginFormPainter implements Painter, Animation {
private double angle;
private final GeneralPath gp = new GeneralPath();
private final Component parentCmp;
private int counter;
public LoginFormPainter(Component parentCmp) {
this.parentCmp = parentCmp;
int x;
int y;
int w = Display.getInstance().convertToPixels(10);
int h = w;
int x0 = getX() - getWidth();
int xn = getX() + 2 * getWidth();
int y0 = getY() - getHeight();
int yn = getY() + 2 * getHeight();
for (int offset : new int[]{0, w/2}) {
x = x0 +offset;
y = y0 + offset;
while (x < xn) {
while (y < yn) {
drawShape(gp, x, y, w, h);
Background Painter
Now that we got this out of the way lets look at the painter code itself… 

This is the rotation angle in degrees. We increment this as part of the animation logic.
class LoginFormPainter implements Painter, Animation {
private double angle;
private final GeneralPath gp = new GeneralPath();
private final Component parentCmp;
private int counter;
public LoginFormPainter(Component parentCmp) {
this.parentCmp = parentCmp;
int x;
int y;
int w = Display.getInstance().convertToPixels(10);
int h = w;
int x0 = getX() - getWidth();
int xn = getX() + 2 * getWidth();
int y0 = getY() - getHeight();
int yn = getY() + 2 * getHeight();
for (int offset : new int[]{0, w/2}) {
x = x0 +offset;
y = y0 + offset;
while (x < xn) {
while (y < yn) {
drawShape(gp, x, y, w, h);
Background Painter
This is the shape object representing the background pattern. We draw it (or stroke it) like a rubber stamp
int w = Display.getInstance().convertToPixels(10);
int h = w;
int x0 = getX() - getWidth();
int xn = getX() + 2 * getWidth();
int y0 = getY() - getHeight();
int yn = getY() + 2 * getHeight();
for (int offset : new int[]{0, w/2}) {
x = x0 +offset;
y = y0 + offset;
while (x < xn) {
while (y < yn) {
drawShape(gp, x, y, w, h);
y += h;
}
x += w;
y = y0 + offset;
}
}
registerAnimated(this);
}
Background Painter
The constructor and the drawShape method create the patten shape that we stroke later. This code happens once to generate the "lines" and we then color them later on
int w = Display.getInstance().convertToPixels(10);
int h = w;
int x0 = getX() - getWidth();
int xn = getX() + 2 * getWidth();
int y0 = getY() - getHeight();
int yn = getY() + 2 * getHeight();
for (int offset : new int[]{0, w/2}) {
x = x0 +offset;
y = y0 + offset;
while (x < xn) {
while (y < yn) {
drawShape(gp, x, y, w, h);
y += h;
}
x += w;
y = y0 + offset;
}
}
registerAnimated(this);
}
Background Painter
The registerAnimated method of Form is needed for low level animations. It triggers invocations of the animate() method with every EDT tick so we can update the
animation state. In this case we change the rotation angle with every tick
registerAnimated(this);
}
private void drawShape(GeneralPath gp, float x, float y, float w, float h) {
float e = w/6;
float ex1 = x + (w-e)/2;
float ex2 = x + (w+e)/2;
float ey1 = y + (h-e)/2;
float ey2 = y + (h+e)/2;
gp.moveTo(ex1, y);
gp.lineTo(ex2, y);
gp.quadTo(x+w, y, x+w, ey1);
gp.lineTo(x+w, ey2);
gp.quadTo(x+w, y+h, ex2, y+h);
gp.lineTo(ex1, y+h);
gp.quadTo(x, y+h, x, ey2);
gp.lineTo(x, ey1);
gp.quadTo(x, y, ex1, y);
}
@Override
public void paint(Graphics g, Rectangle rect) {
g.setAlpha(255);
Background Painter
The drawShape method adds logical lines and quads to the given path. A quad means quadratic curve to the given position. You can see three methods used on the
path element. moveTo moves the virtual pen in the air without drawing anything to a starting point. lineTo draws a line from the last position of the pen to the given
position. quadTo draws a quadratic curve (bezier curve) to the given position through the given curve position.
@Override
public void paint(Graphics g, Rectangle rect) {
g.setAlpha(255);
g.setColor(0x128f96);
g.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
g.setColor(0xffffff);
g.setAlpha(72);
g.setAntiAliased(true);
g.rotate((float)(Math.PI/4f + Math.toRadians(angle % 360)), getX() +
getWidth()/2, getY() + getHeight()/2);
g.drawShape(gp, new Stroke(1.5f, Stroke.CAP_SQUARE, Stroke.JOIN_BEVEL, 1f));
g.resetAffine();
g.setAlpha(255);
}
@Override
public boolean animate() {
counter++;
if(counter % 2 == 0) {
angle += 0.1;
parentCmp.repaint();
}
Background Painter
The paint method is the callback from the painter, we fill the background rotate the graphics context and draw the shapes. 

Notice we just invoke drawShape and it is drawn with the current alpha and color in place
g.resetAffine();
g.setAlpha(255);
}
@Override
public boolean animate() {
counter++;
if(counter % 2 == 0) {
angle += 0.1;
parentCmp.repaint();
}
return false;
}
@Override
public void paint(Graphics g) {
}
}
Background Painter
The low level animation code invokes animate() at fixed intervals based on EDT "heart beats". Normally you would return true to trigger a repaint but here I only want to
repaint a specific component. Notice that I only change the angle and move every other frame to conserve CPU. Also notice I rotate by 0.1 degrees which creates a very
smooth, slow and subtle rotation.
g.resetAffine();
g.setAlpha(255);
}
@Override
public boolean animate() {
counter++;
if(counter % 2 == 0) {
angle += 0.1;
parentCmp.repaint();
}
return false;
}
@Override
public void paint(Graphics g) {
}
}
Background Painter
This paint method belongs to the Animation interface. We don't need it as we always return false.

Once all of this is done the login UI rotates in the background slowly and smoothly. A shadow appears after a second and the UI looks in my opinion as good as the
native UI

More Related Content

PDF
Creating an Uber Clone - Part XXII - Transcript.pdf
PDF
Creating an Uber Clone - Part IV.pdf
PDF
Writeup advanced lane_lines_project
PPTX
java_for_future_15-Multithreaded-Graphics.pptx
PDF
C Graphics Functions
PDF
Sierpinski Triangle - Polyglot FP for Fun and Profit - Haskell and Scala
PDF
Introduction to Coding
PDF
Enhancing UI/UX using Java animations
Creating an Uber Clone - Part XXII - Transcript.pdf
Creating an Uber Clone - Part IV.pdf
Writeup advanced lane_lines_project
java_for_future_15-Multithreaded-Graphics.pptx
C Graphics Functions
Sierpinski Triangle - Polyglot FP for Fun and Profit - Haskell and Scala
Introduction to Coding
Enhancing UI/UX using Java animations

Similar to Creating an Uber Clone - Part IV - Transcript.pdf (20)

PDF
Creating an Uber Clone - Part XXIV - Transcript.pdf
PDF
A Simple 3D Graphics Engine Written in Python and Allegro
PPT
Cpp tutorial
PDF
Augmented reality in web rtc browser
DOCX
Computer graphics
DOCX
Computer graphics
KEY
openFrameworks 007 - graphics
PPT
MIDP: Game API
PPT
Circles graphic
PPT
Shadow Volumes on Programmable Graphics Hardware
PPTX
Trident International Graphics Workshop 2014 1/5
PDF
Webgl para JavaScripters
PDF
Computer Graphics in Java and Scala - Part 1b
PDF
14multithreaded Graphics
PDF
HTML5: where flash isn't needed anymore
KEY
Leaving Flatland: getting started with WebGL
PDF
Computer graphics practical(jainam)
PPTX
Graphics in C++
PPTX
Introduction to graphics programming in c
PDF
bfd23fd7-0d89-45c0-8b82-c991b30ed375.pdf
Creating an Uber Clone - Part XXIV - Transcript.pdf
A Simple 3D Graphics Engine Written in Python and Allegro
Cpp tutorial
Augmented reality in web rtc browser
Computer graphics
Computer graphics
openFrameworks 007 - graphics
MIDP: Game API
Circles graphic
Shadow Volumes on Programmable Graphics Hardware
Trident International Graphics Workshop 2014 1/5
Webgl para JavaScripters
Computer Graphics in Java and Scala - Part 1b
14multithreaded Graphics
HTML5: where flash isn't needed anymore
Leaving Flatland: getting started with WebGL
Computer graphics practical(jainam)
Graphics in C++
Introduction to graphics programming in c
bfd23fd7-0d89-45c0-8b82-c991b30ed375.pdf
Ad

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf
Ad

Recently uploaded (20)

PPTX
sap open course for s4hana steps from ECC to s4
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
Spectroscopy.pptx food analysis technology
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
NewMind AI Weekly Chronicles - August'25-Week II
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPTX
A Presentation on Artificial Intelligence
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
Big Data Technologies - Introduction.pptx
PDF
cuic standard and advanced reporting.pdf
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
sap open course for s4hana steps from ECC to s4
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Spectroscopy.pptx food analysis technology
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
NewMind AI Weekly Chronicles - August'25-Week II
The AUB Centre for AI in Media Proposal.docx
Mobile App Security Testing_ A Comprehensive Guide.pdf
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Building Integrated photovoltaic BIPV_UPV.pdf
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Reach Out and Touch Someone: Haptics and Empathic Computing
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Dropbox Q2 2025 Financial Results & Investor Presentation
Chapter 3 Spatial Domain Image Processing.pdf
A Presentation on Artificial Intelligence
Programs and apps: productivity, graphics, security and other tools
Big Data Technologies - Introduction.pptx
cuic standard and advanced reporting.pdf
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...

Creating an Uber Clone - Part IV - Transcript.pdf

  • 1. Creating an Uber Clone - Part IV In this part we’ll refine the login form and go into low level graphics for animations and background.
  • 2. The Login Form © Codename One 2017 all rights reserved This is how I finished the previous part. I discussed the fonts but there are other differences. Obviously the flag is slightly different since I used our own resource file. But there are two other noticeable differences. The first is the drop shadow behind the logo which is missing. The second is the background rotation of the pattern which is iOS specific in the native app but I don’t see a reason for that. I’d like to have it on Android too…
  • 3. final Image shadow = squareShadow( squareLogo.getPreferredW(), squareLogo.getPreferredH(), convertToPixels(14), 0.35f); Drop Shadow The simplest thing to do is generate a square image of the logo that already has a translucent shadow within. This would be pretty trivial to anyone versed in photoshop and would look great on the device... However, my goal is to teach programming not photoshop so I'm picking the "hard way" of solving this. The Effects class in Codename One allows us to create a shadow image for the given dimensions or image. Since the logo is square we can just use the dimensions approach. The method accepts the size of the shadow, the blur radius which means how far it should go out of the size limits and the opacity as a value between 0 and 1.
  • 4. Container logo = LayeredLayout.encloseIn( BorderLayout.centerAbsolute( new Label(shadow, "Container")), BorderLayout.centerAbsolute(squareLogo) ); Drop Shadow So now we have an image of the shadow but the logo image and background are already fixed so we need something new. Instead of using the logo as is we place the shadow in a layer below using the LayeredLayout and this will produce the desired effect with one HUGE caveat. It's really slow! Shadows are computationally slow… We use gaussian blur to generate shadows and that's a very slow algorithm.
  • 5. Label placeholder = new Label(); Container logo = LayeredLayout.encloseIn( placeholder, BorderLayout.centerAbsolute(squareLogo) ); startThread(() -> { Image shadow = squareShadow(squareLogo.getPreferredW(), squareLogo.getPreferredH(), convertToPixels(14), 0.35f); callSerially(() -> { logo.replace(placeholder, BorderLayout.centerAbsolute( new Label(shadow, "Container")), null); revalidate(); }); }, "Shadow Maker").start(); Drop Shadow The solution is to move that code offline. The UI will appear and the shadow will appear a second later when it's ready. The placeholder is there so we can put the shadow into place when it's ready.
  • 6. Label placeholder = new Label(); Container logo = LayeredLayout.encloseIn( placeholder, BorderLayout.centerAbsolute(squareLogo) ); startThread(() -> { Image shadow = squareShadow(squareLogo.getPreferredW(), squareLogo.getPreferredH(), convertToPixels(14), 0.35f); callSerially(() -> { logo.replace(placeholder, BorderLayout.centerAbsolute( new Label(shadow, "Container")), null); revalidate(); }); }, "Shadow Maker").start(); Drop Shadow When the shadow image is ready we replace it on the EDT with the the new shadow label. The label uses the Container UIID which is always transparent with 0 padding and 0 margin.
  • 7. Background © Codename One 2017 all rights reserved The Android version of Uber doesn't include the rotation animation for reasons that are just unclear to me. I think it might collide with some of the material design transitions or some other problem. It works nicely on all OS's with the way I implemented it. I could just rotate the tiles like the one pictured above and call it a day, the effect would look decent and perform well. However, I wanted better control and in order to get that I need shapes... Shapes allow us to draw arbitrary vectors/curves in a performant way. Since this is effectively a vector API, rotation & scaling don't distort the result. In order to use this API I need to use the low level graphics API and the background painter.
  • 8. logo.getUnselectedStyle().setBgPainter( new LoginFormPainter(logo)); class LoginFormPainter implements Painter, Animation { //.... } Background Painter We can set the painter for the logo object using this code. Notice that normally we don't need a reference to the parent component logo but in this case we need it for the animation. I’ll go into that soon. But first I’d like to say a few words about painters… 
 Styling can only go so far. If you want to customize the background of a component in a completely custom way you can use the Painter API to define the actual rendering of the background. This overrides all style rendering and provides you with a Graphics object you can use for drawing. Notice that the Graphics API is a low level API and might have platform specific behaviors that aren't as refined as the component/style API's. It's harder to optimize low level graphics code so use it with caution.
  • 9. class LoginFormPainter implements Painter, Animation { private double angle; private final GeneralPath gp = new GeneralPath(); private final Component parentCmp; private int counter; public LoginFormPainter(Component parentCmp) { this.parentCmp = parentCmp; int x; int y; int w = Display.getInstance().convertToPixels(10); int h = w; int x0 = getX() - getWidth(); int xn = getX() + 2 * getWidth(); int y0 = getY() - getHeight(); int yn = getY() + 2 * getHeight(); for (int offset : new int[]{0, w/2}) { x = x0 +offset; y = y0 + offset; while (x < xn) { while (y < yn) { drawShape(gp, x, y, w, h); Background Painter Now that we got this out of the way lets look at the painter code itself… This is the rotation angle in degrees. We increment this as part of the animation logic.
  • 10. class LoginFormPainter implements Painter, Animation { private double angle; private final GeneralPath gp = new GeneralPath(); private final Component parentCmp; private int counter; public LoginFormPainter(Component parentCmp) { this.parentCmp = parentCmp; int x; int y; int w = Display.getInstance().convertToPixels(10); int h = w; int x0 = getX() - getWidth(); int xn = getX() + 2 * getWidth(); int y0 = getY() - getHeight(); int yn = getY() + 2 * getHeight(); for (int offset : new int[]{0, w/2}) { x = x0 +offset; y = y0 + offset; while (x < xn) { while (y < yn) { drawShape(gp, x, y, w, h); Background Painter This is the shape object representing the background pattern. We draw it (or stroke it) like a rubber stamp
  • 11. int w = Display.getInstance().convertToPixels(10); int h = w; int x0 = getX() - getWidth(); int xn = getX() + 2 * getWidth(); int y0 = getY() - getHeight(); int yn = getY() + 2 * getHeight(); for (int offset : new int[]{0, w/2}) { x = x0 +offset; y = y0 + offset; while (x < xn) { while (y < yn) { drawShape(gp, x, y, w, h); y += h; } x += w; y = y0 + offset; } } registerAnimated(this); } Background Painter The constructor and the drawShape method create the patten shape that we stroke later. This code happens once to generate the "lines" and we then color them later on
  • 12. int w = Display.getInstance().convertToPixels(10); int h = w; int x0 = getX() - getWidth(); int xn = getX() + 2 * getWidth(); int y0 = getY() - getHeight(); int yn = getY() + 2 * getHeight(); for (int offset : new int[]{0, w/2}) { x = x0 +offset; y = y0 + offset; while (x < xn) { while (y < yn) { drawShape(gp, x, y, w, h); y += h; } x += w; y = y0 + offset; } } registerAnimated(this); } Background Painter The registerAnimated method of Form is needed for low level animations. It triggers invocations of the animate() method with every EDT tick so we can update the animation state. In this case we change the rotation angle with every tick
  • 13. registerAnimated(this); } private void drawShape(GeneralPath gp, float x, float y, float w, float h) { float e = w/6; float ex1 = x + (w-e)/2; float ex2 = x + (w+e)/2; float ey1 = y + (h-e)/2; float ey2 = y + (h+e)/2; gp.moveTo(ex1, y); gp.lineTo(ex2, y); gp.quadTo(x+w, y, x+w, ey1); gp.lineTo(x+w, ey2); gp.quadTo(x+w, y+h, ex2, y+h); gp.lineTo(ex1, y+h); gp.quadTo(x, y+h, x, ey2); gp.lineTo(x, ey1); gp.quadTo(x, y, ex1, y); } @Override public void paint(Graphics g, Rectangle rect) { g.setAlpha(255); Background Painter The drawShape method adds logical lines and quads to the given path. A quad means quadratic curve to the given position. You can see three methods used on the path element. moveTo moves the virtual pen in the air without drawing anything to a starting point. lineTo draws a line from the last position of the pen to the given position. quadTo draws a quadratic curve (bezier curve) to the given position through the given curve position.
  • 14. @Override public void paint(Graphics g, Rectangle rect) { g.setAlpha(255); g.setColor(0x128f96); g.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); g.setColor(0xffffff); g.setAlpha(72); g.setAntiAliased(true); g.rotate((float)(Math.PI/4f + Math.toRadians(angle % 360)), getX() + getWidth()/2, getY() + getHeight()/2); g.drawShape(gp, new Stroke(1.5f, Stroke.CAP_SQUARE, Stroke.JOIN_BEVEL, 1f)); g.resetAffine(); g.setAlpha(255); } @Override public boolean animate() { counter++; if(counter % 2 == 0) { angle += 0.1; parentCmp.repaint(); } Background Painter The paint method is the callback from the painter, we fill the background rotate the graphics context and draw the shapes. Notice we just invoke drawShape and it is drawn with the current alpha and color in place
  • 15. g.resetAffine(); g.setAlpha(255); } @Override public boolean animate() { counter++; if(counter % 2 == 0) { angle += 0.1; parentCmp.repaint(); } return false; } @Override public void paint(Graphics g) { } } Background Painter The low level animation code invokes animate() at fixed intervals based on EDT "heart beats". Normally you would return true to trigger a repaint but here I only want to repaint a specific component. Notice that I only change the angle and move every other frame to conserve CPU. Also notice I rotate by 0.1 degrees which creates a very smooth, slow and subtle rotation.
  • 16. g.resetAffine(); g.setAlpha(255); } @Override public boolean animate() { counter++; if(counter % 2 == 0) { angle += 0.1; parentCmp.repaint(); } return false; } @Override public void paint(Graphics g) { } } Background Painter This paint method belongs to the Animation interface. We don't need it as we always return false. Once all of this is done the login UI rotates in the background slowly and smoothly. A shadow appears after a second and the UI looks in my opinion as good as the native UI