From bdbf052aaf1e824f221057ca3c3acae80d1a521d Mon Sep 17 00:00:00 2001 From: Material Design Team Date: Thu, 17 Feb 2022 17:10:26 +0000 Subject: [PATCH] [M3][Color] Refactor DynamicColors overloading methods and added DynamicColorsOptions class to support color harmonization. PiperOrigin-RevId: 429324480 --- .../android/material/color/DynamicColors.java | 81 +++++++--- .../material/color/DynamicColorsOptions.java | 148 ++++++++++++++++++ 2 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 lib/java/com/google/android/material/color/DynamicColorsOptions.java diff --git a/lib/java/com/google/android/material/color/DynamicColors.java b/lib/java/com/google/android/material/color/DynamicColors.java index e271566f8d9..1d8ff50baf8 100644 --- a/lib/java/com/google/android/material/color/DynamicColors.java +++ b/lib/java/com/google/android/material/color/DynamicColors.java @@ -111,13 +111,6 @@ public boolean isSupported() { private static final int USE_DEFAULT_THEME_OVERLAY = 0; - private static final Precondition ALWAYS_ALLOW = new Precondition() { - @Override - public boolean shouldApplyDynamicColors(@NonNull Activity activity, int theme) { - return true; - } - }; - private DynamicColors() {} /** @@ -130,7 +123,7 @@ private DynamicColors() {} * @param application The target application. */ public static void applyToActivitiesIfAvailable(@NonNull Application application) { - applyToActivitiesIfAvailable(application, USE_DEFAULT_THEME_OVERLAY); + applyIfAvailable(new DynamicColorsOptions.Builder(application).build()); } /** @@ -145,7 +138,7 @@ public static void applyToActivitiesIfAvailable(@NonNull Application application */ public static void applyToActivitiesIfAvailable( @NonNull Application application, @StyleRes int theme) { - applyToActivitiesIfAvailable(application, theme, ALWAYS_ALLOW); + applyIfAvailable(new DynamicColorsOptions.Builder(application).setThemeOverlay(theme).build()); } /** @@ -160,7 +153,8 @@ public static void applyToActivitiesIfAvailable( */ public static void applyToActivitiesIfAvailable( @NonNull Application application, @NonNull Precondition precondition) { - applyToActivitiesIfAvailable(application, USE_DEFAULT_THEME_OVERLAY, precondition); + applyIfAvailable( + new DynamicColorsOptions.Builder(application).setPrecondition(precondition).build()); } /** @@ -189,8 +183,11 @@ public static void applyToActivitiesIfAvailable( */ public static void applyToActivitiesIfAvailable( @NonNull Application application, @StyleRes int theme, @NonNull Precondition precondition) { - application.registerActivityLifecycleCallbacks( - new DynamicColorsActivityLifecycleCallbacks(theme, precondition)); + applyIfAvailable( + new DynamicColorsOptions.Builder(application) + .setThemeOverlay(theme) + .setPrecondition(precondition) + .build()); } /** @@ -200,7 +197,7 @@ public static void applyToActivitiesIfAvailable( * @param activity The target activity. */ public static void applyIfAvailable(@NonNull Activity activity) { - applyIfAvailable(activity, USE_DEFAULT_THEME_OVERLAY); + applyIfAvailable(new DynamicColorsOptions.Builder(activity).build()); } /** @@ -210,7 +207,7 @@ public static void applyIfAvailable(@NonNull Activity activity) { * @param theme The resource ID of the theme overlay that provides dynamic color definition. */ public static void applyIfAvailable(@NonNull Activity activity, @StyleRes int theme) { - applyIfAvailable(activity, theme, ALWAYS_ALLOW); + applyIfAvailable(new DynamicColorsOptions.Builder(activity).setThemeOverlay(theme).build()); } /** @@ -222,11 +219,41 @@ public static void applyIfAvailable(@NonNull Activity activity, @StyleRes int th */ public static void applyIfAvailable( @NonNull Activity activity, @NonNull Precondition precondition) { - applyIfAvailable(activity, USE_DEFAULT_THEME_OVERLAY, precondition); + applyIfAvailable( + new DynamicColorsOptions.Builder(activity).setPrecondition(precondition).build()); + } + + /** + * Applies dynamic colors to the given application/activity with theme overlay according to the + * precondition specified in the dynamic colors options. + * + * @param dynamicColorsOptions The dynamic colors options object that specifies the theme resource + * ID, precondition to decide if dynamic colors should be applied and the callback function + * for after dynamic colors have been applied. + */ + public static void applyIfAvailable(@NonNull DynamicColorsOptions dynamicColorsOptions) { + Application application = dynamicColorsOptions.getApplication(); + Activity activity = dynamicColorsOptions.getActivity(); + if (application != null) { + application.registerActivityLifecycleCallbacks( + new DynamicColorsActivityLifecycleCallbacks(dynamicColorsOptions)); + } else if (activity != null) { + applyIfAvailable( + activity, + dynamicColorsOptions.getThemeOverlay(), + dynamicColorsOptions.getPrecondition(), + dynamicColorsOptions.getOnAppliedCallback()); + } else { + // This should not happen according to the design of DynamicColorsOptions. + throw new IllegalArgumentException("Either Application or Activity is required."); + } } private static void applyIfAvailable( - @NonNull Activity activity, @StyleRes int theme, @NonNull Precondition precondition) { + @NonNull Activity activity, + @StyleRes int theme, + @NonNull Precondition precondition, + @NonNull OnAppliedCallback onAppliedCallback) { if (!isDynamicColorAvailable()) { return; } @@ -235,6 +262,7 @@ private static void applyIfAvailable( } if (theme != 0 && precondition.shouldApplyDynamicColors(activity, theme)) { applyDynamicColorThemeOverlay(activity, theme); + onAppliedCallback.onApplied(activity); } } @@ -339,20 +367,29 @@ public interface Precondition { boolean shouldApplyDynamicColors(@NonNull Activity activity, @StyleRes int theme); } + /** The interface that provides a callback method after dynamic colors have been applied. */ + public interface OnAppliedCallback { + + /** The callback method after dynamic colors have been applied. */ + void onApplied(@NonNull Activity activity); + } + private static class DynamicColorsActivityLifecycleCallbacks implements ActivityLifecycleCallbacks { - private final int dynamicColorThemeOverlay; - private final Precondition precondition; + private final DynamicColorsOptions dynamicColorsOptions; - DynamicColorsActivityLifecycleCallbacks(@StyleRes int theme, @NonNull Precondition condition) { - dynamicColorThemeOverlay = theme; - precondition = condition; + DynamicColorsActivityLifecycleCallbacks(@NonNull DynamicColorsOptions options) { + this.dynamicColorsOptions = options; } @Override public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { - applyIfAvailable(activity, dynamicColorThemeOverlay, precondition); + applyIfAvailable( + activity, + dynamicColorsOptions.getThemeOverlay(), + dynamicColorsOptions.getPrecondition(), + dynamicColorsOptions.getOnAppliedCallback()); } @Override diff --git a/lib/java/com/google/android/material/color/DynamicColorsOptions.java b/lib/java/com/google/android/material/color/DynamicColorsOptions.java new file mode 100644 index 00000000000..feac8cb1331 --- /dev/null +++ b/lib/java/com/google/android/material/color/DynamicColorsOptions.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.color; + +import android.app.Activity; +import android.app.Application; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import com.google.android.material.color.DynamicColors.OnAppliedCallback; +import com.google.android.material.color.DynamicColors.Precondition; + +/** Wrapper class for specifying dynamic colors options when applying dynamic colors. */ +public class DynamicColorsOptions { + + private static final Precondition ALWAYS_ALLOW = + new Precondition() { + @Override + public boolean shouldApplyDynamicColors(@NonNull Activity activity, int theme) { + return true; + } + }; + + private static final OnAppliedCallback NO_OP_CALLBACK = + new OnAppliedCallback() { + @Override + public void onApplied(@NonNull Activity activity) {} + }; + + @Nullable private final Application application; + @Nullable private final Activity activity; + @StyleRes private final int themeOverlay; + @NonNull private final Precondition precondition; + @NonNull private final OnAppliedCallback onAppliedCallback; + + private DynamicColorsOptions(Builder builder) { + this.application = builder.application; + this.activity = builder.activity; + this.themeOverlay = builder.themeOverlay; + this.precondition = builder.precondition; + this.onAppliedCallback = builder.onAppliedCallback; + } + + /** Returns the application where dynamic color is applied. */ + @Nullable + public Application getApplication() { + return application; + } + + /** Returns the activity where dynamic color is applied. */ + @Nullable + public Activity getActivity() { + return activity; + } + + /** Returns the resource ID of the theme overlay that provides dynamic color definition. */ + @StyleRes + public int getThemeOverlay() { + return themeOverlay; + } + + /** Returns the precondition that decides if dynamic colors should be applied. */ + @NonNull + public Precondition getPrecondition() { + return precondition; + } + + /** Returns the callback method after dynamic colors have been applied. */ + @NonNull + public OnAppliedCallback getOnAppliedCallback() { + return onAppliedCallback; + } + + /** + * Builder class for specifying options when applying dynamic colors. When building {@code + * DynamicColorOptions}, either an {@link Application} or {@link Activity} is required. When + * {@link Application} is specified, dynamic colors will be applied to all activities in the + * application. For example: + * + * DynamicColorOptions dynamicColorOptions = + * new DynamicColorsOptions.Builder(application) + * .setThemeOverlay(themeOverlay) + * .setPrecondition(precondition) + * .setOnAppliedCallback(onAppliedCallback) + * .build() + * + */ + public static class Builder { + + @Nullable private final Application application; + @Nullable private final Activity activity; + @StyleRes private int themeOverlay; + @NonNull private Precondition precondition = ALWAYS_ALLOW; + @NonNull private OnAppliedCallback onAppliedCallback = NO_OP_CALLBACK; + + /** Sets the Application to apply dynamic colors on. */ + public Builder(@NonNull Application application) { + this.application = application; + this.activity = null; + } + + /** Sets the Activity to apply dynamic colors on. */ + public Builder(@NonNull Activity activity) { + this.activity = activity; + this.application = null; + } + + /** Sets the resource ID of the theme overlay that provides dynamic color definition. */ + @NonNull + public Builder setThemeOverlay(@StyleRes int themeOverlay) { + this.themeOverlay = themeOverlay; + return this; + } + + /** Sets the precondition that decides if dynamic colors should be applied. */ + @NonNull + public Builder setPrecondition(@NonNull Precondition precondition) { + this.precondition = precondition; + return this; + } + + /** Sets the callback method for after the dynamic colors have been applied. */ + @NonNull + public Builder setOnAppliedCallback(@NonNull OnAppliedCallback onAppliedCallback) { + this.onAppliedCallback = onAppliedCallback; + return this; + } + + @NonNull + public DynamicColorsOptions build() { + return new DynamicColorsOptions(this); + } + } +}