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();