Creating an Uber Clone - Part XXII - Transcript.pdf
1. Creating an Uber Clone - Part XXII
With that out of the way search should work but how does it display the result?
For that we need to add additional features to the MapForm that address these capabilities. Before we go into that lets check out what that means
3. private Component createNavigationTag(String location, int durationMinutes) {
Label locationLabel = new Label(location, "NavigationLabel");
if(durationMinutes > 0) {
Label duration = new Label("" + durationMinutes,
"NavigationMinuteLabel");
Label min = new Label("MIN", "NavigationMinuteDescLabel");
Container west = BoxLayout.encloseY(duration, min);
Container result = BorderLayout.centerEastWest(locationLabel,
null, west);
result.getUnselectedStyle().setBorder(BlackAndWhiteBorder.create().
blackLinePosition(west.getPreferredW()));
return result;
}
locationLabel.getUnselectedStyle().setBorder(
BlackAndWhiteBorder.create());
return locationLabel;
}
addMapListener
Lets start with creating these tags. The tag code itself is trivial it's just a Label or a Container with some details. Nothing special.
Except for one important detail, the BlackAndWhiteBorder. In order to implement the unique shape of the tag I created a new Border class. Notice I used the preferred
width of the west component to determine the black section. This is done in the blackLinePosition method.
Before we go any further I’d just like to make one point clear: I Would have Used a 9-piece Border. If this were a real application I would have just cut a 9-piece border
and moved on. However, since the point is teaching I chose to do this the "hard way"
15. public class BlackAndWhiteBorder extends Border {
private static final String CACHE_KEY = "cn1$$-bwcache";
private final float shadowBlur = 10;
private final float shadowSpread;
private final int shadowOpacity = 110;
private final float cornerRadius = 1f;
private int blackLinePosition = -1;
BlackAndWhiteBorder() {
shadowSpread = Display.getInstance().convertToPixels(0.2f);
}
public static BlackAndWhiteBorder create() {
return new BlackAndWhiteBorder();
}
public BlackAndWhiteBorder blackLinePosition(int blackLinePosition) {
this.blackLinePosition = blackLinePosition;
return this;
}
private Image createTargetImage(Component c, int w, int h, boolean fast) {
Image target = Image.createImage(w, h, 0);
int shapeX = 0;
int shapeY = 0;
int shapeW = w;
int shapeH = h;
BlackAndWhiteBorder
Now that these are out of the way let's take a look at the border. Notice we can just subclass the Border class just like we can implement painters etc. This provides a
similar path for customization but is sometimes more flexible. Most of this code is based on the builtin RoundRectBorder class.
Drawing this type of border is pretty expensive so we draw onto an image and place that image in cache within the component using putClientProperty we use this value
as the key
16. public class BlackAndWhiteBorder extends Border {
private static final String CACHE_KEY = "cn1$$-bwcache";
private final float shadowBlur = 10;
private final float shadowSpread;
private final int shadowOpacity = 110;
private final float cornerRadius = 1f;
private int blackLinePosition = -1;
BlackAndWhiteBorder() {
shadowSpread = Display.getInstance().convertToPixels(0.2f);
}
public static BlackAndWhiteBorder create() {
return new BlackAndWhiteBorder();
}
public BlackAndWhiteBorder blackLinePosition(int blackLinePosition) {
this.blackLinePosition = blackLinePosition;
return this;
}
private Image createTargetImage(Component c, int w, int h, boolean fast) {
Image target = Image.createImage(w, h, 0);
int shapeX = 0;
int shapeY = 0;
int shapeW = w;
int shapeH = h;
BlackAndWhiteBorder
The blackLinePosition is used in the version of this border that's partially black
17. private Image createTargetImage(Component c, int w, int h, boolean fast) {
Image target = Image.createImage(w, h, 0);
int shapeX = 0;
int shapeY = 0;
int shapeW = w;
int shapeH = h;
Graphics tg = target.getGraphics();
tg.setAntiAliased(true);
int shadowSpreadL = Display.getInstance().convertToPixels(shadowSpread);
shapeW -= shadowSpreadL;
shapeH -= shadowSpreadL;
shapeX += Math.round(((float)shadowSpreadL) * 0.9);
shapeY += Math.round(((float)shadowSpreadL) * 0.9);
for(int iter = shadowSpreadL - 1 ; iter >= 0 ; iter--) {
tg.translate(iter, iter);
fillShape(tg, 0, shadowOpacity / shadowSpreadL, w-(iter*2),h - (iter * 2));
tg.translate(-iter, -iter);
}
if(Display.getInstance().isGaussianBlurSupported() && !fast) {
Image blured = Display.getInstance().gaussianBlurImage(target,shadowBlur/2);
target = Image.createImage(w, h, 0);
tg = target.getGraphics();
tg.drawImage(blured, 0, 0);
BlackAndWhiteBorder
Here we create the border image that we will cache for the given component. Since a shadow is set on the border and that can take some processing power. To speed
this up we have two versions of the method fast and slow. We call the fast one and invoke the slow one asynchronously to update the border
18. private Image createTargetImage(Component c, int w, int h, boolean fast) {
Image target = Image.createImage(w, h, 0);
int shapeX = 0;
int shapeY = 0;
int shapeW = w;
int shapeH = h;
Graphics tg = target.getGraphics();
tg.setAntiAliased(true);
int shadowSpreadL = Display.getInstance().convertToPixels(shadowSpread);
shapeW -= shadowSpreadL;
shapeH -= shadowSpreadL;
shapeX += Math.round(((float)shadowSpreadL) * 0.9);
shapeY += Math.round(((float)shadowSpreadL) * 0.9);
for(int iter = shadowSpreadL - 1 ; iter >= 0 ; iter--) {
tg.translate(iter, iter);
fillShape(tg, 0, shadowOpacity / shadowSpreadL, w-(iter*2),h - (iter * 2));
tg.translate(-iter, -iter);
}
if(Display.getInstance().isGaussianBlurSupported() && !fast) {
Image blured = Display.getInstance().gaussianBlurImage(target,shadowBlur/2);
target = Image.createImage(w, h, 0);
tg = target.getGraphics();
tg.drawImage(blured, 0, 0);
BlackAndWhiteBorder
We do a shadow effect by drawing a gradient with varying alpha degrees then blurring that out
19. tg.drawImage(blured, 0, 0);
tg.setAntiAliased(true);
}
tg.translate(shapeX, shapeY);
c.getStyle().setBorder(Border.createEmpty());
GeneralPath gp = createShape(shapeW, shapeH);
tg.setClip(gp);
c.getStyle().getBgPainter().paint(tg, new Rectangle(0, 0, w, h));
c.getStyle().setBorder(this);
return target;
}
public void paintBorderBackground(Graphics g, final Component c) {
final int w = c.getWidth();
final int h = c.getHeight();
int x = c.getX();
int y = c.getY();
if(w > 0 && h > 0) {
Image background = (Image)c.getClientProperty(CACHE_KEY);
if(background!=null && background.getWidth()==w && background.getHeight()==h){
g.drawImage(background, x, y);
return;
}
BlackAndWhiteBorder
If we have a cached version of the border image we will just use that as the background of the component assuming the size of the component didn't change
20. int y = c.getY();
if(w > 0 && h > 0) {
Image background = (Image)c.getClientProperty(CACHE_KEY);
if(background!=null && background.getWidth()==w && background.getHeight()==h){
g.drawImage(background, x, y);
return;
}
} else {
return;
}
Image target = createTargetImage(c, w, h, true);
g.drawImage(target, x, y);
c.putClientProperty(CACHE_KEY, target);
Display.getInstance().callSeriallyOnIdle(new Runnable() {
public void run() {
if(w == c.getWidth() && h == c.getHeight()) {
Image target = createTargetImage(c, w, h, false);
c.putClientProperty(CACHE_KEY, target);
c.repaint();
}
}
BlackAndWhiteBorder
Otherwise we create that image and update it later with the slower version that includes the gradient shadow effect
21. c.putClientProperty(CACHE_KEY, target);
c.repaint();
}
}
});
}
private GeneralPath createShape(int shapeW, int shapeH) {
GeneralPath gp = new GeneralPath();
float radius = Display.getInstance().convertToPixels(cornerRadius);
float x = 0;
float y = 0;
float widthF = shapeW;
float heightF = shapeH;
gp.moveTo(x + radius, y);
if(blackLinePosition > -1) {
gp.lineTo(x + widthF, y);
} else {
gp.lineTo(x + widthF - radius, y);
gp.quadTo(x + widthF, y, x + widthF, y + radius);
}
gp.lineTo(x + widthF, y + heightF - radius);
gp.quadTo(x + widthF, y + heightF, x + widthF - radius, y + heightF);
if(blackLinePosition > -1) {
BlackAndWhiteBorder
We create the shape of the component. If it's the one with the black line we place the corner in the top right otherwise we place it in the bottom left
22. gp.quadTo(x + widthF, y + heightF, x + widthF - radius, y + heightF);
if(blackLinePosition > -1) {
gp.lineTo(x + radius, y + heightF);
gp.quadTo(x, y + heightF, x, y + heightF - radius);
} else {
gp.lineTo(x, y + heightF);
}
gp.lineTo(x, y + radius);
gp.quadTo(x, y, x + radius, y);
gp.closePath();
return gp;
}
public int getMinimumHeight() {
return convertToPixels(shadowSpread) + convertToPixels(cornerRadius) * 2;
}
public int getMinimumWidth() {
return convertToPixels(shadowSpread) + convertToPixels(cornerRadius) * 2;
}
private void fillShape(Graphics g, int color, int opacity, int width, int height) {
g.setColor(0xffffff);
g.setAlpha(255);
GeneralPath gp = createShape(width, height);
if(blackLinePosition > -1) {
BlackAndWhiteBorder
We can now fill out the shape as part of the image creation code if we have a black line we do the fill operation twice with different clip sizes to create that effect