dynamic_colors.md 12 KB

How to Develop With Dynamic Colors

Background

Dynamic Color is a new feature introduced as part of Android S (aka Android 12/API 31) and Material You in which the applications can be themed using a custom color palette extracted from the user’s wallpaper.

How does it work?

The Material 3 theme contains color attributes that correspond to the material “color roles.” When we call DynamicColors#applyIfAvailable() in ChromeBaseAppCompatActivity#onCreate (or any other activity’s #onCreate method), the color role attributes are overridden using colors that are extracted by the system from the user’s wallpaper. Finally, any UI surface that references the color role attributes gets dynamically colored. The color role attributes can be found here.

Using one of the color attributes, colorPrimary, as an example:

?attr/colorPrimary -> @color/baseline_primary_600 -> #0B57D0

becomes

?attr/colorPrimary -> @android:color/system_accent1_600 -> #616200

once the dynamic colors are applied.

How to…

Basically, the app UI surfaces need to directly or indirectly reference the color role attributes rather than color resources. There are different ways to achieve this depending on the situation.

Semantic names

Semantic names are used to color the UI components that share the same meaning or role consistently throughout the application. For example, default_icon_color can be used almost anywhere to tint a primary icon. A list of common semantic names can be found in semantic_colors_dynamic.xml. Before we needed to support dynamic colors, semantic names were defined as @color resources that could reference other colors. Now that the colors need to reference attributes, it is not possible to continue using @colors because Android does not support referencing an ?attr from a @color, so now we use @macros or color state lists.

Macros

In xml, semantic names for dynamic colors are defined using <macro> tags. A macro is replaced with the value it holds at the build time, similar to the C++ macros. Googlers can learn more about macros here. Macros can be used in xml to color views and drawables similarly to the color resources (@color/) or theme attributes (?attr/). Unlike colors, macros are not resources. So, macros cannot be declared in non-default configurations, e.g. values-night.

Utility methods

At the time of writing, there is no support for macros in Java code. So, we have created utility classes with static methods to access the semantic names in code. For example, if a semantic name is defined as @macro/default_bg_color in xml, it would also have a utility method SemanticColorUtils#getDefaultBgColor() in Java.

Location

While the most common semantic names are defined in semantic_colors_dynamic.xml, feature or surface specific semantic names can be defined in their relative modules or directories. Then, they can reference other macros or directly reference theme attributes. For example, suggestion_url_color is defined in chrome/browser/ui/android/omnibox/ and it references @macro/default_text_color_link, a common semantic name. If this semantic color needs to be accessed from Java code, a utility method can be added to the utility class for that directory, e.g. ChromeSemanticColorUtils.

Color state lists

Color state lists are defined using the <selector> tag and are usually used to update the visual representation of the views based on their state, e.g. enabled vs disabled. Unlike the regular color resources, color state lists can reference attributes (and macros). Color state lists can be used to color surfaces as long as they point to dynamic colors, e.g. default_text_color_accent1_tint_list. Keep in mind that there may be limitations to the color state list usage. For example, color state lists cannot be used as android:background below API 29.

Surface colors

Surface colors represent ?attr/colorSurface at different surface levels or elevation values. These colors are calculated at runtime, so there is no macro or attribute that can be used to retrieve surface colors. For this reason, there are currently 2 ways to calculate surface colors.

ChromeColors#getSurfaceColor()

ChromeColors#getSurfaceColor() calculates a surface color using the required attributes from the provided Context and the elevation resource. The elevation resource should be one of the predefined elevation levels, or a resource that points to one of these.

SurfaceColorDrawable

SurfaceColorDrawable is a custom drawable that automatically calculates its surface color based on the provided app:surfaceElevation attribute. This can be used in xml to define a drawable similarly to <shape>, e.g. oval_surface_1. Similar to the ChromeColors#getSurfaceColor() method, the provided app:surfaceElevation should be one of the predefined elevation levels (mentioned above). The only limitation with SurfaceColorDrawable is that custom drawables are only supported API 24 and above. So, until the minimum API level for Chrome is increased from 23 to 24, this method will require keeping 2 versions of the same drawable: one for drawable-v23 and one for drawable-v24+.

Illustrations

The guidance for illustrations is still a work in progress. Until it is finalized, the recommended approach is continuing the use of non-dynamic colors (see the non-dynamic colors section below) but making the illustration’s background transparent. In the future, we may recommend using color resources that point to system color resources.

Launcher widgets

The guidance for widgets is not finalized, so the instructions on the Enhance your widget developer page can be followed. However, the mentioned “device theming” approach is extremely limited. At some point, we may recommend using system color resources similarly to the illustrations.

Non-dynamic colors

Not all colors can or should be dynamic. Some examples are migration-to-dynamic-colors-is-work-in-progress surfaces, incognito surfaces, and WebView. Non-dynamic colors still follow the semantic name pattern to keep colors consistent throughout Chrome. They are typically defined in semantic_colors_adaptive.xml, which contains colors that adapt to day and night modes and are suffixed with _baseline to indicate that they are not dynamic, or semantic_colors_non_adaptive.xml which do not adapt for day night mode and may be used for e.g. incognito coloring.

Migration-to-dynamic-colors-is-work-in-progress

You may notice that there are still semantic colors that are defined as colors and not macros or some random colors being used on some surfaces. If you notice anything like this on a surface you own or maintain, please look at migrating those colors to dynamic colors. Otherwise, you can file a bug using this link.

Incognito surfaces

Incognito surfaces should not be dynamically colored. Instead, they should be colored using the night mode baseline colors. This means using colors such as default_icon_color_light instead of macros or attributes.

Colors used by WebView

Dynamic colors depend on the theme attributes defined in a Context’s Theme. In the case of WebView, the Context is not controlled by Chrome but by the embedder application. We cannot be sure that the embedder application is going to provide all attributes required by our dynamic color implementation. For this reason, any UI surface that can be depended on by WebView should avoid using dynamic colors. For shared widgets like ButtonCompat, the colors can be injected through the constructor, or a custom styleable attribute can be used to manipulate the colors when the widget is used within Chrome.

Colors and widgets that are shared between WebView and Chrome are typically defined in the ui/android directory. If this is not the case for a color or widget, they can be moved to components/browser_ui.

Getting colors and drawables

The method used to retrieve colors or drawables needs to have access to the theme, or Context, to be able to resolve the ?attrs. These are the commonly used methods in Chrome:

  • Context#getColor(int): Used to get non-dynamic colors or the default color for color state lists.
  • AppCompatResources#getColorStateList(Context, int): Used to get color state lists.
  • AppCompatResources#getDrawable(Context, int): Used to get drawables.