Java SWT:徽章通知
Java SWT : Badge Notifications
我有一个基于桌面的 UI 应用程序,它是在 windows 上用 Java SWT 运行 编写的。
我想在 UI 屏幕上添加一个按钮,其行为应该类似于 iphone 或 facebook 通知上的徽章,如下图所示。
徽章上的数字是动态的,会根据待处理通知的数量增加或减少。
如何在 SWT/AWT 中实现类似的功能?
IOS徽章:
Facebook 通知:
我最近实现了类似的东西。您可以简单地使用 GC
绘制自定义图像,然后覆盖在您想要的图标上。
我在这里包括我的助手 class。这不是最干净的代码(很多东西都是硬编码的),但您会明白这一点的。通知气泡会根据通知数量(最大 999)自行调整大小。
使用方法(记得缓存and/or处理你的图片!):
Image decoratedIcon = new ImageOverlayer()
.baseImage(baseImage) // You icon/badget
.overlayImage(ImageOverlayer.createNotifImage(5)) // 5 notifications
.overlayImagePosition(OverlayedImagePosition.TOP_RIGHT)
.createImage();
/**
* <pre>
* The difference between this and the ImageBuilder is
* that ImageOverlayer does not chain the images, rather
* just overlays them one onto another.
*
*
* Rules:
*
* 1.) Images are not disposed. Resource handing must be done externally.
* 2.) Only two images allowed, for now.
* 3.) The size of the composite image should normally be the size of the
* base image, BUT: if the overlaying image is larger, then larger
* parameters are grabbed, and the base image is still underneath.
* 4.) Use the builder APIs to set the base and overlaying images. The
* position of the overlaying image is optional, and CENTER by default.
* When you've set these, simply call createImage()
*
* Further improvements:
*
* - Combine this with ImageBuilder. These two composers should be welded.
*
* </pre>
*
* @author grec.georgian@gmail.com
*
*/
public class ImageOverlayer extends CompositeImageDescriptor
{
// ==================== 1. Static Fields ========================
public enum OverlayedImagePosition
{
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER;
}
// ====================== 2. Instance Fields =============================
private ImageData baseImageData;
private ImageData overlayedImageData;
private OverlayedImagePosition overlayedImagePosition = OverlayedImagePosition.CENTER;
// ==================== 3. Static Methods ====================
/**
* Creates a red circle with a white bold number inside it.
* Does not cache the final image.
*/
public static final Image createNotifImage(final int numberOfNotifications)
{
// Initial width and height - hardcoded for now
final int width = 14;
int height = 14;
// Initial font size
int fontSize = 100;
int decorationWidth = width;
String textToDraw = String.valueOf(numberOfNotifications);
final int numberLength = Integer.toString(numberOfNotifications).length();
if(numberLength > 3)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
// - smaller font size so the new text will fit(set to 999+) if we have
// a number of notifications with more than 3 digits
decorationWidth += numberLength * 2;
height -= 4;
fontSize = 80;
textToDraw = "999+"; //$NON-NLS-1$
}
else if (numberLength > 2)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
decorationWidth += numberLength * 1.5;
height -= 4;
}
final Font font = new Font(Display.getDefault(), "Arial", width / 2, SWT.BOLD); //$NON-NLS-1$
final Image canvas = new Image(null, decorationWidth, height);
final GC gc = new GC(canvas);
gc.setAntialias(SWT.ON);
gc.setAlpha(0);
gc.fillRectangle(0, 0, decorationWidth, height);
gc.setAlpha(255);
gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
// spetrila, 2014.12.17: In case we have more than two digits in the number of notifications,
// we will change the decoration to a rounded rectangle so it can contain
// all of the digits in the notification number
if(decorationWidth == width)
gc.fillOval(0, 0, decorationWidth - 1, height - 1);
else
gc.fillRoundRectangle(0, 0, decorationWidth, height, 10, 10);
final FontData fontData = font.getFontData()[0];
fontData.setHeight((int) (fontData.getHeight() * fontSize / 100.0 + 0.5));
fontData.setStyle(SWT.BOLD);
final Font newFont = new Font(Display.getCurrent(), fontData);
// gc.setFont(AEFUIActivator.getDefault().getCustomizedFont(font, fontSize, SWT.BOLD));
gc.setFont(newFont);
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
final Point textSize = gc.stringExtent(textToDraw);
final int xPos = (decorationWidth - textSize.x) / 2;
final int yPos = (height - textSize.y) / 2;
gc.drawText(textToDraw, xPos + 1, yPos, true);
gc.dispose();
final ImageData imgData = canvas.getImageData();
// Remove white transparent pixels
final int whitePixel = imgData.palette.getPixel(new RGB(255,255,255));
imgData.transparentPixel = whitePixel;
final Image finalImage = new Image(null, imgData);
canvas.dispose();
font.dispose();
newFont.dispose();
return finalImage;
}
// ==================== 5. Creators ====================
@Override
public Image createImage()
{
if (baseImageData == null || overlayedImageData == null)
throw new IllegalArgumentException("Please check the ImageOverlayer. One of the overlaying images is NULL."); //$NON-NLS-1$
return super.createImage();
}
// ==================== 6. Action Methods ====================
@Override
protected void drawCompositeImage(final int width, final int height)
{
/*
* These two determine where the overlayed image top left
* corner should go, relative to the base image behind it.
*/
int xPos = 0;
int yPos = 0;
switch (overlayedImagePosition)
{
case TOP_LEFT:
break;
case TOP_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
break;
case BOTTOM_LEFT:
yPos = baseImageData.height - overlayedImageData.height;
break;
case BOTTOM_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
yPos = baseImageData.height - overlayedImageData.height;
break;
case CENTER:
xPos = (baseImageData.width - overlayedImageData.width) / 2;
yPos = (baseImageData.height - overlayedImageData.height) / 2;
break;
default:
break;
}
drawImage(baseImageData, 0, 0);
drawImage(overlayedImageData, xPos, yPos);
}
// ==================== 7. Getters & Setters ====================
final public ImageOverlayer overlayImagePosition(final OverlayedImagePosition overlayImagePosition)
{
this.overlayedImagePosition = overlayImagePosition;
return this;
}
final public ImageOverlayer baseImage(final ImageData baseImageData)
{
this.baseImageData = baseImageData;
return this;
}
final public ImageOverlayer baseImage(final Image baseImage)
{
this.baseImageData = baseImage.getImageData();
return this;
}
final public ImageOverlayer baseImage(final ImageDescriptor baseImageDescriptor)
{
this.baseImageData = baseImageDescriptor.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageData overlayImageData)
{
this.overlayedImageData = overlayImageData;
return this;
}
final public ImageOverlayer overlayImage(final Image overlayImage)
{
this.overlayedImageData = overlayImage.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageDescriptor overlayImageDescriptor)
{
this.overlayedImageData = overlayImageDescriptor.getImageData();
return this;
}
@Override
protected Point getSize()
{
// The size of the composite image is determined by the maximum size between the two building images,
// although keep in mind that the base image always comes underneath the overlaying one.
return new Point( max(baseImageData.width, overlayedImageData.width), max(baseImageData.height, overlayedImageData.height) );
}
}
您也可以为此使用控件装饰。优点是您可以使用 hide()
和 show()
方法轻松 hide/show 通知,并向其添加工具提示文本和侦听器。
检查此 blog 以了解如何使用控件修饰。根据您的情况使用 Button
小部件而不是 Text
。
如下所示创建通知图像并将其设置为 ControlDecoration
对象。
Image image = new Image(display, 20, 25);
GC gc = new GC(image);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, 20, 25);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
int notif = 5;
gc.drawText(new Integer(notif).toString(), 5, 5);
gc.dispose();
我有一个基于桌面的 UI 应用程序,它是在 windows 上用 Java SWT 运行 编写的。
我想在 UI 屏幕上添加一个按钮,其行为应该类似于 iphone 或 facebook 通知上的徽章,如下图所示。
徽章上的数字是动态的,会根据待处理通知的数量增加或减少。
如何在 SWT/AWT 中实现类似的功能?
IOS徽章:
Facebook 通知:
我最近实现了类似的东西。您可以简单地使用 GC
绘制自定义图像,然后覆盖在您想要的图标上。
我在这里包括我的助手 class。这不是最干净的代码(很多东西都是硬编码的),但您会明白这一点的。通知气泡会根据通知数量(最大 999)自行调整大小。
使用方法(记得缓存and/or处理你的图片!):
Image decoratedIcon = new ImageOverlayer()
.baseImage(baseImage) // You icon/badget
.overlayImage(ImageOverlayer.createNotifImage(5)) // 5 notifications
.overlayImagePosition(OverlayedImagePosition.TOP_RIGHT)
.createImage();
/**
* <pre>
* The difference between this and the ImageBuilder is
* that ImageOverlayer does not chain the images, rather
* just overlays them one onto another.
*
*
* Rules:
*
* 1.) Images are not disposed. Resource handing must be done externally.
* 2.) Only two images allowed, for now.
* 3.) The size of the composite image should normally be the size of the
* base image, BUT: if the overlaying image is larger, then larger
* parameters are grabbed, and the base image is still underneath.
* 4.) Use the builder APIs to set the base and overlaying images. The
* position of the overlaying image is optional, and CENTER by default.
* When you've set these, simply call createImage()
*
* Further improvements:
*
* - Combine this with ImageBuilder. These two composers should be welded.
*
* </pre>
*
* @author grec.georgian@gmail.com
*
*/
public class ImageOverlayer extends CompositeImageDescriptor
{
// ==================== 1. Static Fields ========================
public enum OverlayedImagePosition
{
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER;
}
// ====================== 2. Instance Fields =============================
private ImageData baseImageData;
private ImageData overlayedImageData;
private OverlayedImagePosition overlayedImagePosition = OverlayedImagePosition.CENTER;
// ==================== 3. Static Methods ====================
/**
* Creates a red circle with a white bold number inside it.
* Does not cache the final image.
*/
public static final Image createNotifImage(final int numberOfNotifications)
{
// Initial width and height - hardcoded for now
final int width = 14;
int height = 14;
// Initial font size
int fontSize = 100;
int decorationWidth = width;
String textToDraw = String.valueOf(numberOfNotifications);
final int numberLength = Integer.toString(numberOfNotifications).length();
if(numberLength > 3)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
// - smaller font size so the new text will fit(set to 999+) if we have
// a number of notifications with more than 3 digits
decorationWidth += numberLength * 2;
height -= 4;
fontSize = 80;
textToDraw = "999+"; //$NON-NLS-1$
}
else if (numberLength > 2)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
decorationWidth += numberLength * 1.5;
height -= 4;
}
final Font font = new Font(Display.getDefault(), "Arial", width / 2, SWT.BOLD); //$NON-NLS-1$
final Image canvas = new Image(null, decorationWidth, height);
final GC gc = new GC(canvas);
gc.setAntialias(SWT.ON);
gc.setAlpha(0);
gc.fillRectangle(0, 0, decorationWidth, height);
gc.setAlpha(255);
gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
// spetrila, 2014.12.17: In case we have more than two digits in the number of notifications,
// we will change the decoration to a rounded rectangle so it can contain
// all of the digits in the notification number
if(decorationWidth == width)
gc.fillOval(0, 0, decorationWidth - 1, height - 1);
else
gc.fillRoundRectangle(0, 0, decorationWidth, height, 10, 10);
final FontData fontData = font.getFontData()[0];
fontData.setHeight((int) (fontData.getHeight() * fontSize / 100.0 + 0.5));
fontData.setStyle(SWT.BOLD);
final Font newFont = new Font(Display.getCurrent(), fontData);
// gc.setFont(AEFUIActivator.getDefault().getCustomizedFont(font, fontSize, SWT.BOLD));
gc.setFont(newFont);
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
final Point textSize = gc.stringExtent(textToDraw);
final int xPos = (decorationWidth - textSize.x) / 2;
final int yPos = (height - textSize.y) / 2;
gc.drawText(textToDraw, xPos + 1, yPos, true);
gc.dispose();
final ImageData imgData = canvas.getImageData();
// Remove white transparent pixels
final int whitePixel = imgData.palette.getPixel(new RGB(255,255,255));
imgData.transparentPixel = whitePixel;
final Image finalImage = new Image(null, imgData);
canvas.dispose();
font.dispose();
newFont.dispose();
return finalImage;
}
// ==================== 5. Creators ====================
@Override
public Image createImage()
{
if (baseImageData == null || overlayedImageData == null)
throw new IllegalArgumentException("Please check the ImageOverlayer. One of the overlaying images is NULL."); //$NON-NLS-1$
return super.createImage();
}
// ==================== 6. Action Methods ====================
@Override
protected void drawCompositeImage(final int width, final int height)
{
/*
* These two determine where the overlayed image top left
* corner should go, relative to the base image behind it.
*/
int xPos = 0;
int yPos = 0;
switch (overlayedImagePosition)
{
case TOP_LEFT:
break;
case TOP_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
break;
case BOTTOM_LEFT:
yPos = baseImageData.height - overlayedImageData.height;
break;
case BOTTOM_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
yPos = baseImageData.height - overlayedImageData.height;
break;
case CENTER:
xPos = (baseImageData.width - overlayedImageData.width) / 2;
yPos = (baseImageData.height - overlayedImageData.height) / 2;
break;
default:
break;
}
drawImage(baseImageData, 0, 0);
drawImage(overlayedImageData, xPos, yPos);
}
// ==================== 7. Getters & Setters ====================
final public ImageOverlayer overlayImagePosition(final OverlayedImagePosition overlayImagePosition)
{
this.overlayedImagePosition = overlayImagePosition;
return this;
}
final public ImageOverlayer baseImage(final ImageData baseImageData)
{
this.baseImageData = baseImageData;
return this;
}
final public ImageOverlayer baseImage(final Image baseImage)
{
this.baseImageData = baseImage.getImageData();
return this;
}
final public ImageOverlayer baseImage(final ImageDescriptor baseImageDescriptor)
{
this.baseImageData = baseImageDescriptor.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageData overlayImageData)
{
this.overlayedImageData = overlayImageData;
return this;
}
final public ImageOverlayer overlayImage(final Image overlayImage)
{
this.overlayedImageData = overlayImage.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageDescriptor overlayImageDescriptor)
{
this.overlayedImageData = overlayImageDescriptor.getImageData();
return this;
}
@Override
protected Point getSize()
{
// The size of the composite image is determined by the maximum size between the two building images,
// although keep in mind that the base image always comes underneath the overlaying one.
return new Point( max(baseImageData.width, overlayedImageData.width), max(baseImageData.height, overlayedImageData.height) );
}
}
您也可以为此使用控件装饰。优点是您可以使用 hide()
和 show()
方法轻松 hide/show 通知,并向其添加工具提示文本和侦听器。
检查此 blog 以了解如何使用控件修饰。根据您的情况使用 Button
小部件而不是 Text
。
如下所示创建通知图像并将其设置为 ControlDecoration
对象。
Image image = new Image(display, 20, 25);
GC gc = new GC(image);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, 20, 25);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
int notif = 5;
gc.drawText(new Integer(notif).toString(), 5, 5);
gc.dispose();