Skip to content

Commit b70bbc2

Browse files
Material Design Teamveganafro
authored andcommitted
[M3][Color] Iteration on HarmonizedColors and DynamicColors feedback
Removed the Context/Application/Activity in the Builders class. Updated docs accordingly and added examples explaining how harmonizing color attributes with theme overlay works. PiperOrigin-RevId: 433769484
1 parent ca8594d commit b70bbc2

File tree

7 files changed

+276
-165
lines changed

7 files changed

+276
-165
lines changed

catalog/java/io/material/catalog/preferences/DynamicColorPreference.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,14 @@ protected boolean shouldRecreateActivityOnOptionChanged() {
8989
}
9090

9191
private void applyDynamicColorsWithMaterialDefaultHarmonization(@NonNull Context context) {
92-
DynamicColors.applyIfAvailable(
93-
new DynamicColorsOptions.Builder((Application) context.getApplicationContext())
92+
DynamicColors.applyToActivitiesIfAvailable(
93+
(Application) context.getApplicationContext(),
94+
new DynamicColorsOptions.Builder()
9495
.setPrecondition(precondition)
9596
.setOnAppliedCallback(
9697
activity ->
97-
HarmonizedColors.applyIfAvailable(
98-
HarmonizedColorsOptions.createMaterialDefaults(activity)))
98+
HarmonizedColors.applyToContextIfAvailable(
99+
activity, HarmonizedColorsOptions.createMaterialDefaults()))
99100
.build());
100101
}
101102
}

docs/theming/Color.md

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -228,24 +228,39 @@ If the app is running on Android S+, dynamic colors will be applied to the
228228
activity. You can also apply a custom theme overlay or a precondition as
229229
depicted in the application section above.
230230

231-
##### Apply dynamic colors using `DynamicColorsOptions`
231+
##### Apply dynamic colors to all activities in the app using `DynamicColorsOptions`
232232

233-
You also have the option to apply dynamic colors by passing in a
234-
`DynamicColorsOptions` object. When constructing `DynamicColorsOptions`, an
235-
Application or Activity is required as to where dynamic colors will be applied.
236-
You may optionally specify a customized theme overlay, likely inheriting from the
237-
`Material3` theme overlays above and/or a precondition, to have finer control over
238-
theme overlay deployment. You may also optionally specify a `OnAppliedCallback`
239-
function, which will be called after dynamic colors have been applied:
233+
You also have the option to apply dynamic colors to all activities in the app by
234+
passing in a `DynamicColorsOptions` object. When constructing
235+
`DynamicColorsOptions`, you may optionally specify a customized theme overlay,
236+
likely inheriting from the `Material3` theme overlays above and/or a
237+
precondition, to have finer control over theme overlay deployment. You may also
238+
optionally specify an `OnAppliedCallback` function, which will be called after
239+
dynamic colors have been applied:
240240

241241
```
242242
DynamicColorsOptions dynamicColorOptions =
243-
new DynamicColorsOptions.Builder(application)
243+
new DynamicColorsOptions.Builder()
244244
.setThemeOverlay(themeOverlay)
245245
.setPrecondition(precondition)
246246
.setOnAppliedCallback(onAppliedCallback)
247247
.build()
248-
DynamicColors.applyIfAvailable(dynamicColorOptions);
248+
DynamicColors.applyToActivitiesIfAvailable(application, dynamicColorOptions);
249+
```
250+
251+
##### Apply dynamic colors to a specific activity using `DynamicColorsOptions`
252+
253+
You can also apply dynamic colors to a specific activity in the app by passing
254+
in the specific activity and a `DynamicColorsOptions` object:
255+
256+
```
257+
DynamicColorsOptions dynamicColorOptions =
258+
new DynamicColorsOptions.Builder()
259+
.setThemeOverlay(themeOverlay)
260+
.setPrecondition(precondition)
261+
.setOnAppliedCallback(onAppliedCallback)
262+
.build()
263+
DynamicColors.applyToActivityIfAvailable(activity, dynamicColorOptions);
249264
```
250265

251266
##### Apply dynamic colors to a specific fragment/view
@@ -394,30 +409,41 @@ harmonization won't happen and `colorToHarmonize` will be returned.
394409

395410
##### Color Resources Harmonization
396411

397-
If you need to harmonize color resources at runtime and use the harmonized color
398-
resources in xml, call:
399-
400-
```
401-
HarmonizedColors.applyIfAvailable(harmonizedColorsOptions);
402-
```
403-
To construct a `HarmonizedColorsOptions`, a `Context` is required to specify
404-
where the harmonized color resources will be applied. You can optionally pass in
405-
an array of resource ids for the color resources you'd like to harmonize, a
412+
We've provided the `HarmonizedColors` and `HarmonizedColorsOptions` classes in
413+
the `com.google.android.material.color` package for color resources
414+
harmonization. `HarmonizedColorsOptions.Builder` is a Builder class and to
415+
construct a `HarmonizedColorsOptions`. You can optionally pass in an array of
416+
resource ids for the color resources you'd like to harmonize, a
406417
`HarmonizedColorAttributes` object and/or the color attribute to harmonize with:
407418

408419
```
409420
HarmonizedColorsOptions options =
410-
new HarmonizedColorsOptions.Builder(activity)
421+
new HarmonizedColorsOptions.Builder()
411422
.setColorResourceIds(colorResources)
412423
.setColorAttributes(HarmonizedColorAttributes.create(attributes))
413424
.setColorAttributeToHarmonizeWith(colorAttributeResId)
414425
.build();
415426
```
416427

428+
In the `HarmonizedColorsOptions` class, we also provided a convenience method
429+
`createMaterialDefaults()`, with Error colors being harmonized by default.
430+
431+
```
432+
HarmonizedColorsOptions options = HarmonizedColorsOptions.createMaterialDefaults();
433+
HarmonizedColors.applyToContextIfAvailable(context, options);
434+
```
435+
436+
If you need to harmonize color resources at runtime to a context and use the
437+
harmonized color resources in xml, call:
438+
439+
```
440+
HarmonizedColors.applyToContextIfAvailable(context, harmonizedColorsOptions);
441+
```
442+
417443
To return a new `Context` with color resources being harmonized, call:
418444

419445
```
420-
HarmonizedColors.wrapContextIfAvailable(harmonizedColorsOptions);
446+
HarmonizedColors.wrapContextIfAvailable(context, harmonizedColorsOptions);
421447
```
422448

423449
##### `HarmonizedColorAttributes`
@@ -430,14 +456,63 @@ Static Factory Methods | Descr
430456

431457
If the first static factory method is used, the color resource's id and value of
432458
the attribute will be resolved at runtime and the color resources will be
433-
harmonized. If you're concerned about accidentally overwriting color resources,
434-
the second method should be used. In this method, instead of the color resource
435-
that the attributes are pointing to being harmonized directly, resources value
436-
in the theme overlay will be replaced instead.
459+
harmonized.
460+
461+
**Note:** The way we harmonize color attributes is by looking up the color
462+
resource the attribute points to, and harmonizing the color resource directly.
463+
If you are looking to harmonize only color resources, in most cases when
464+
constructing `HarmonizedColorsOptions`, the
465+
`setColorResourceIds(colorResources)` method should be enough.
466+
467+
If you're concerned about accidentally overwriting color resources, the second
468+
static factory method should be used. In this method, instead of the color
469+
resource that the color attribute is pointing to in the main theme/context being
470+
harmonized directly, the color resources pointed by the color attributes after
471+
the theme overlay is applied will be harmonized. In the theme overlay, the color
472+
resources pointed by the color attributes are dummy values, to avoid color
473+
resources that the color attributs are pointing to in the main theme/context be
474+
overridden.
475+
476+
Here is an example of how we harmonize Error colors with theme overlay, to avoid
477+
accidentally overriding the resources from the main theme/context. We have an
478+
array of color attributes defined as:
479+
480+
```
481+
private static final int[] HARMONIZED_MATERIAL_ATTRIBUTES =
482+
new int[] {
483+
R.attr.colorError,
484+
R.attr.colorOnError,
485+
R.attr.colorErrorContainer,
486+
R.attr.colorOnErrorContainer
487+
};
488+
```
489+
490+
And a theme overlay defined as:
491+
492+
```
493+
<style name="ThemeOverlay.Material3.HarmonizedColors" parent="">
494+
<item name="colorError">@color/material_harmonized_color_error</item>
495+
<item name="colorOnError">@color/material_harmonized_color_on_error</item>
496+
<item name="colorErrorContainer">@color/material_harmonized_color_error_container</item>
497+
<item name="colorOnErrorContainer">@color/material_harmonized_color_on_error_container</item>
498+
</style>
499+
```
500+
501+
With this theme overlay, instead of directly overwriting the resources that
502+
`colorError`, `colorOnError`, `colorErrorContainer`, and `colorOnErrorContainer`
503+
point to in the main theme/context, we would:
504+
505+
1. look up the resource values in the `Context` themed by the theme overlay
506+
2. retrieve the harmonized resources with Primary
507+
3. override `@color/material_harmonized_color_error`,
508+
`@color/material_harmonized_color_on_error`, etc. with the harmonized colors
509+
510+
That way the Error roles in the theme overlay would point to harmonized
511+
resources.
437512

438-
If you would like to harmonize additional color attributes along with the
439-
attributes in `HarmonizedColorAttributes.createMaterialDefaults()`,
440-
the `HarmonizedColorAttributes` would look like:
513+
If you would like to harmonize additional color attributes along with
514+
harmonizing Error roles by default, the `HarmonizedColorAttributes` would look
515+
like:
441516

442517
```
443518
HarmonizedColorAttributes.create(
@@ -462,10 +537,11 @@ DynamicColorsOptions dynamicColorOptions =
462537
...
463538
.setOnAppliedCallback(
464539
activity ->
465-
HarmonizedColors.applyIfAvailable(
466-
HarmonizedColorsOptions.createMaterialDefaults(activity)))
540+
HarmonizedColors.applyToContextIfAvailable(
541+
activity,
542+
HarmonizedColorsOptions.createMaterialDefaults()))
467543
.build()
468-
DynamicColors.applyIfAvailable(dynamicColorOptions);
544+
DynamicColors.applyToActivityIfAvailable(activity, dynamicColorOptions);
469545
```
470546

471547
For color ressources harmonization in a fragment/view, you would use the context
@@ -478,10 +554,12 @@ harmonization:
478554
Context newContext = DynamicColors.wrapContextIfAvailable(getContext());
479555
480556
HarmonizedColorsOptions options =
481-
new HarmonizedColorsOptions.Builder(newContext)
557+
new HarmonizedColorsOptions.Builder()
482558
.setColorResources(colorResources)
483559
.build();
484-
HarmonizedColors.wrapContextIfAvailable(options);
560+
Context harmonizedContext = HarmonizedColors.wrapContextIfAvailable(newContext, options);
561+
// Usage example with the new harmonizedContext.
562+
MaterialColors.getColor(harmonizedContext, R.attr.customColor, -1);
485563
```
486564

487565
**Note:** This is only supported for API 30 and above.

lib/java/com/google/android/material/color/DynamicColors.java

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private DynamicColors() {}
118118
* @param application The target application.
119119
*/
120120
public static void applyToActivitiesIfAvailable(@NonNull Application application) {
121-
applyIfAvailable(new DynamicColorsOptions.Builder(application).build());
121+
applyToActivitiesIfAvailable(application, new DynamicColorsOptions.Builder().build());
122122
}
123123

124124
/**
@@ -132,7 +132,8 @@ public static void applyToActivitiesIfAvailable(@NonNull Application application
132132
*/
133133
public static void applyToActivitiesIfAvailable(
134134
@NonNull Application application, @StyleRes int theme) {
135-
applyIfAvailable(new DynamicColorsOptions.Builder(application).setThemeOverlay(theme).build());
135+
applyToActivitiesIfAvailable(
136+
application, new DynamicColorsOptions.Builder().setThemeOverlay(theme).build());
136137
}
137138

138139
/**
@@ -147,8 +148,8 @@ public static void applyToActivitiesIfAvailable(
147148
*/
148149
public static void applyToActivitiesIfAvailable(
149150
@NonNull Application application, @NonNull Precondition precondition) {
150-
applyIfAvailable(
151-
new DynamicColorsOptions.Builder(application).setPrecondition(precondition).build());
151+
applyToActivitiesIfAvailable(
152+
application, new DynamicColorsOptions.Builder().setPrecondition(precondition).build());
152153
}
153154

154155
/**
@@ -179,21 +180,36 @@ public static void applyToActivitiesIfAvailable(
179180
*/
180181
public static void applyToActivitiesIfAvailable(
181182
@NonNull Application application, @StyleRes int theme, @NonNull Precondition precondition) {
182-
applyIfAvailable(
183-
new DynamicColorsOptions.Builder(application)
183+
applyToActivitiesIfAvailable(
184+
application,
185+
new DynamicColorsOptions.Builder()
184186
.setThemeOverlay(theme)
185187
.setPrecondition(precondition)
186188
.build());
187189
}
188190

191+
/**
192+
* Applies dynamic colors to the given application with {@link DynamicColorsOptions} provided.
193+
*
194+
* @param application The target application.
195+
* @param dynamicColorsOptions The dynamic colors options object that specifies the theme resource
196+
* ID, precondition to decide if dynamic colors should be applied and the callback function
197+
* for after dynamic colors have been applied.
198+
*/
199+
public static void applyToActivitiesIfAvailable(
200+
@NonNull Application application, @NonNull DynamicColorsOptions dynamicColorsOptions) {
201+
application.registerActivityLifecycleCallbacks(
202+
new DynamicColorsActivityLifecycleCallbacks(dynamicColorsOptions));
203+
}
204+
189205
/**
190206
* Applies dynamic colors to the given activity with the theme overlay designated by the theme
191207
* attribute {@code dynamicColorThemeOverlay}.
192208
*
193209
* @param activity The target activity.
194210
*/
195211
public static void applyIfAvailable(@NonNull Activity activity) {
196-
applyIfAvailable(new DynamicColorsOptions.Builder(activity).build());
212+
applyToActivityIfAvailable(activity, new DynamicColorsOptions.Builder().build());
197213
}
198214

199215
/**
@@ -203,7 +219,8 @@ public static void applyIfAvailable(@NonNull Activity activity) {
203219
* @param theme The resource ID of the theme overlay that provides dynamic color definition.
204220
*/
205221
public static void applyIfAvailable(@NonNull Activity activity, @StyleRes int theme) {
206-
applyIfAvailable(new DynamicColorsOptions.Builder(activity).setThemeOverlay(theme).build());
222+
applyToActivityIfAvailable(
223+
activity, new DynamicColorsOptions.Builder().setThemeOverlay(theme).build());
207224
}
208225

209226
/**
@@ -215,37 +232,28 @@ public static void applyIfAvailable(@NonNull Activity activity, @StyleRes int th
215232
*/
216233
public static void applyIfAvailable(
217234
@NonNull Activity activity, @NonNull Precondition precondition) {
218-
applyIfAvailable(
219-
new DynamicColorsOptions.Builder(activity).setPrecondition(precondition).build());
235+
applyToActivityIfAvailable(
236+
activity, new DynamicColorsOptions.Builder().setPrecondition(precondition).build());
220237
}
221238

222239
/**
223-
* Applies dynamic colors to the given application/activity with theme overlay according to the
224-
* precondition specified in the dynamic colors options.
240+
* Applies dynamic colors to the given activity with {@link DynamicColorsOptions} provided.
225241
*
242+
* @param activity The target activity.
226243
* @param dynamicColorsOptions The dynamic colors options object that specifies the theme resource
227244
* ID, precondition to decide if dynamic colors should be applied and the callback function
228245
* for after dynamic colors have been applied.
229246
*/
230-
public static void applyIfAvailable(@NonNull DynamicColorsOptions dynamicColorsOptions) {
231-
Application application = dynamicColorsOptions.getApplication();
232-
Activity activity = dynamicColorsOptions.getActivity();
233-
if (application != null) {
234-
application.registerActivityLifecycleCallbacks(
235-
new DynamicColorsActivityLifecycleCallbacks(dynamicColorsOptions));
236-
} else if (activity != null) {
237-
applyIfAvailable(
238-
activity,
239-
dynamicColorsOptions.getThemeOverlay(),
240-
dynamicColorsOptions.getPrecondition(),
241-
dynamicColorsOptions.getOnAppliedCallback());
242-
} else {
243-
// This should not happen according to the design of DynamicColorsOptions.
244-
throw new IllegalArgumentException("Either Application or Activity is required.");
245-
}
247+
public static void applyToActivityIfAvailable(
248+
@NonNull Activity activity, @NonNull DynamicColorsOptions dynamicColorsOptions) {
249+
applyToActivityIfAvailable(
250+
activity,
251+
dynamicColorsOptions.getThemeOverlay(),
252+
dynamicColorsOptions.getPrecondition(),
253+
dynamicColorsOptions.getOnAppliedCallback());
246254
}
247255

248-
private static void applyIfAvailable(
256+
private static void applyToActivityIfAvailable(
249257
@NonNull Activity activity,
250258
@StyleRes int theme,
251259
@NonNull Precondition precondition,
@@ -353,7 +361,7 @@ private static class DynamicColorsActivityLifecycleCallbacks
353361
@Override
354362
public void onActivityPreCreated(@NonNull Activity activity,
355363
@Nullable Bundle savedInstanceState) {
356-
applyIfAvailable(
364+
applyToActivityIfAvailable(
357365
activity,
358366
dynamicColorsOptions.getThemeOverlay(),
359367
dynamicColorsOptions.getPrecondition(),

0 commit comments

Comments
 (0)