Getting colors like Android’s media style notifications

Android's media style notification
Android’s media style notification

Update There is a new library by Mateusz Kaflowski who was kind enough to post it in the comments. The tutorial below gives you information on developing your own color fetching script and how to tweak it to your liking, but if you want to make sure the colors match the notification exactly, I recommend you use his. More info here.


I was recently writing an Audiobook app and wanted to get dynamic colors based on the book cover similar to Android’s media style notifications. Obviously the best method is to use the Palette API, but implementing it for the most aesthetically pleasing colors with good contrast wasn’t immediately clear.

Phonograph Screenshot
Phonograph’s colors with white buttons and text

Some popular apps like Pocketcasts for Phonograph seem to get the most vibrant color they can find from the art and then darken it so the white buttons and text will have enough contrast. I used this method for previous apps since it was easy but this method bothered me since it was possible that the primary color wouldn’t match very well and often clashed with the notification.

To better match Android’s notifications we need to also fetch a complementary color from the art that has enough contrast for good readability.

To start, let’s generate a palette:

public static Palette generatePalette(Bitmap bitmap) {
    if (bitmap == null) return null;
    Palette palette = new Palette.Builder(bitmap)
            .generate();
    if(palette.getSwatches().size() <= 1) {
        palette = new Palette.Builder(bitmap)
                .clearFilters()
                .generate();
    }
    return palette;
}

There are a couple things to note here. By default, the palette builder has filters that disable colors that are close to black or white or certain hues that might cause accessibility issues for color blind folks like me. The second part in green will clear all filters in case these are the only colors that can be found. You can even add your own filters using addFilter.

Next, we need to be able to easily figure out the distance between two LAB colors. We can do this easily with ColorUtils.

private static double calculateDistance(int color0, int color1) {
    double[] lab0 = new double[3];
    double[] lab1 = new double[3];

    ColorUtils.colorToLAB(color0, lab0);
    ColorUtils.colorToLAB(color1, lab1);

    return ColorUtils.distanceEuclidean(lab0, lab1);
}

Now we need a way to compare different swatches to find the swatch with the greatest distance.

private static class distanceComparator implements Comparator<Palette.Swatch> {
    private int color;
    private distanceComparator(int color) {
        this.color = color;
    }

    @Override
    public int compare(Palette.Swatch swatch1, Palette.Swatch swatch2) {
        return (int)(calculateDistance(swatch2.getRgb(), color)
                - calculateDistance(swatch1.getRgb(), color));
    }
}

Okay. Now everything is set up to get the colors.

public static int[] getMatchingColors(Palette palette, int[]fallback, boolean invert) {
    if (palette != null) {
        if(palette.getDominantSwatch() != null) {
            Palette.Swatch swatch = palette.getDominantSwatch();
            int swatchColor = swatch.getRgb();
            int matchingColor = -1;
            List<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches());
            
            Collections.sort(swatches, new distanceComparator(swatchColor));
            for(Palette.Swatch contrastingSwatch : swatches) {
                Cat.d("Testing contrast to " + ColorUtils.calculateContrast(contrastingSwatch.getRgb(), swatchColor));
                if(ColorUtils.calculateContrast(contrastingSwatch.getRgb(), swatchColor) > MIN_ALPHA_CONTRAST) {
                    matchingColor = swatches.get(0).getRgb();
                    break;
                }
            }
            if(matchingColor == -1)
                matchingColor = swatch.getBodyTextColor();

            if(invert)
                return new int[]{matchingColor, swatchColor};
            return new int[]{swatchColor, matchingColor};
        }
    }
    return fallback;
}

Some explanation here. getDominantSwatch() gets the swatch generated that appears the most in the bitmap and is close to what notifications use for the background.

Next we can get a list of all the swatches that were generated in the palette and use the comparator we made earlier to sort them by the greatest distance. We can then loop through them making sure the swatch also has a high enough contrast for readability. More information can be found in the Material guidelines for this.

If no matching color can be found, we can use getBodyTextColor() to get a guaranteed contrasting color. (this will either be white or black with some alpha)

I also added an invert option to my function in case a user wants to invert the colors for their audiobook to make it lighter or darker.

NavBooks Screenshot
The completed result in NavBooks.

The result gives you colors that are generally very close to the notification, but may still not match up perfectly. I think there are a couple things that may factor into the difference such as the bitmap being resized. I suspect the notification may also be getting the dominant color from the left side of the image for the  gradient it uses. You can try to get these to match better if you want, but I think the colors generated with this method will get as good results.

Do you have any other methods for generating colors that you use?

11 thoughts on “Getting colors like Android’s media style notifications”

    1. I address this in the last paragraph of the post. I don’t know there code exactly, so I’m not sure exactly

      • What size the bitmap is being rescaled to.This can affect what colors the api gets
      • What area of the image they are getting the colors from. I suspect they are getting the dominant color from the left side of the image for a smooth gradient on the notification. You can read more here.
    1. Thanks for the library. I’d like to try it out, but syncing always fails when trying to follow your instructions in the documentation. Could you test this to see if it works for you?

      Thanks!

      -Joel

  1. JOELPHILIPPAGE would it be possible for you to whip up a example app like mkaflowski did with just one image though.
    I know that’s asking a lot. Thanks

Leave a Reply to mkaflowski Cancel reply

Your email address will not be published. Required fields are marked *