com.pranavpandey.smallapp.theme.DynamicTheme.java Source code

Java tutorial

Introduction

Here is the source code for com.pranavpandey.smallapp.theme.DynamicTheme.java

Source

/*
 * Copyright (C) 2016 Pranav Pandey
 *
 * 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.pranavpandey.smallapp.theme;

import java.util.Locale;

import com.pranavpandey.smallapp.R;

import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ScaleDrawable;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;

/**
 * A helper class to change colors dynamically.
 */
public class DynamicTheme {

    /**
     * Calculate tint based on a given color for better readability.
     *
     * @param color whose tint to be calculated.
     *
     * @return Tint of color.
     */
    public static @ColorInt int getTintColor(@ColorInt int color) {
        int finalColor;
        double tint;
        int a = Color.alpha(color);
        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);
        double colorDarkness = getColorDarkness(color);
        boolean isColorDark;

        if (isColorDark = isColorDark(color)) {
            if (colorDarkness >= 0.5f && colorDarkness < 0.6f) {
                tint = 0.9f;
            } else if (colorDarkness >= 0.6f && colorDarkness < 0.65f) {
                tint = 0.8f;
            } else {
                tint = 0.7f;
            }
            finalColor = Color.argb(a, (int) (r + (tint * (255 - r))), (int) (g + (tint * (255 - g))),
                    (int) (b + (tint * (255 - b))));
        } else {
            if (colorDarkness < 0.5f && colorDarkness >= 0.4f) {
                tint = 0.4f;
            } else if (colorDarkness < 0.4f && colorDarkness >= 0.3f) {
                tint = 0.5f;
            } else {
                tint = 0.6f;
            }
            finalColor = Color.argb((int) Math.min(1.33 * a, 255), (int) (r * tint), (int) (g * tint),
                    (int) (b * tint));
        }

        if (calculateContrast(color, finalColor) < 0.3f) {
            int newTint = 25;
            if (isColorDark) {
                newTint = 225;
            }
            finalColor = Color.argb(a, newTint, newTint, newTint);
        }
        return finalColor;
    }

    /**
     * Calculate accent based on a given color for dynamic theme generation.
     * Still in beta so, sometimes may be inaccurate colors.
     *
     * @param color whose accent to be calculated.
     *
     * @return Accent based on given color.
     */
    public static @ColorInt int getAccentColor(@ColorInt int color) {
        int finalColor;
        int a = Color.alpha(color);
        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);
        double Y = ((r * 299) + (g * 587) + (b * 114)) / 1000;

        int rc = b ^ 0x55;
        int gc = g & 0xFA;
        int bc = r ^ 0x55;

        finalColor = Color.argb(a, rc, gc, bc);
        int r1 = Color.red(finalColor);
        int g1 = Color.green(finalColor);
        int b1 = Color.blue(finalColor);
        double YC = ((r1 * 299) + (g1 * 587) + (b1 * 114)) / 1000;

        int CD = (Math.max(r, r1) - Math.min(r, r1)) + (Math.max(g, g1) - Math.min(g, g1))
                + (Math.max(b, b1) - Math.min(b, b1));
        if ((Y - YC <= 50) && CD <= 200) {
            rc = b ^ 0xFA;
            gc = g & 0x55;
            bc = r ^ 0x55;
        }

        finalColor = Color.argb(a, rc, gc, bc);
        return finalColor;
    }

    /**
     * Calculate contrast of a color based on the give base color so
     * that it will be visible always on top of the base color.
     *
     * @param color whose contrast to be calculated.
     * @param color Background color to calculate contrast.
     *
     * @return Contrast of the given color according to the base color.
     */
    public static @ColorInt int getContrastColor(@ColorInt int color, @ColorInt int contrastWith) {
        if (calculateContrast(contrastWith, color) < 0.3f) {
            return DynamicTheme.getTintColor(color);
        }

        return color;
    }

    /**
     * Detect light or dark color.
     *
     * @param color whose darkness to be calculated.
     *
     * @return <code>true</code> if color is dark.
     */
    public static boolean isColorDark(@ColorInt int color) {
        return getColorDarkness(color) >= 0.5;
    }

    /**
     * Calculate darkness of a color.
     *
     * @param color whose darkness to be calculated.
     *
     * @return Darkness of color. Less that or equal to 1. 0 for white and 1 for black.
     */
    public static double getColorDarkness(@ColorInt int color) {
        return 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
    }

    /**
     * Calculate luma value according to XYZ color space of a color.
     *
     * @param color whose XyzLuma to be calculated.
     *
     * @return luma value according to to XYZ color space in the range 0.0 - 1.0.
     */
    private static float calculateXyzLuma(@ColorInt int color) {
        return (0.2126f * Color.red(color) + 0.7152f * Color.green(color) + 0.0722f * Color.blue(color)) / 255f;
    }

    /**
     * Calculate color contrast difference between two colors based
     * on luma value according to XYZ color space.
     *
     * @param color1 First color to calculate contrast difference.
     * @param color2 Second color to calculate contrast difference.
     *
     * @return color contrast between the two colors.
     *
     * @see {@link #calculateXyzLuma(int)}
     */
    public static float calculateContrast(int color1, int color2) {
        return Math.abs(calculateXyzLuma(color1) - calculateXyzLuma(color2));
    }

    /**
     * Colorize and return the mutated drawable so that, all other references
     * do not change.
     *
     * @param drawable to be colorized.
     * @param color to colorize the drawable.
     *
     * @return Colorized drawable.
     */
    public static Drawable colorizeDrawable(Drawable drawable, @ColorInt int color) {
        drawable.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
        return drawable;
    }

    /**
     * Colorize and return the mutated drawable so that, all other references
     * do not change.
     *
     * @param context to retrieve drawable resource.
     * @param drawableId Id of the drawable to be colorized.
     * @param color to colorize the drawable.
     *
     * @return Colorized drawable.
     */
    public static Drawable colorizeDrawableRes(Context context, @DrawableRes int drawableId, @ColorInt int color) {
        Drawable coloredDrawable = ContextCompat.getDrawable(context, drawableId);
        return colorizeDrawable(coloredDrawable, color);
    }

    /**
     * Highlight the query text within a TextView. Suitable for notifying user about the
     * searched query found in the adapter. TextView should not be empty. Please set your
     * default text first then, highlight the query text by using this function.
     *
     * @param query String to be highlighted.
     * @param textView to set the highlighted text.
     * @param color of the highlighted text.
     *
     * @see {@link android.text.Spannable}.
     */
    public static void highlightQueryTextColor(@NonNull String query, @NonNull TextView textView,
            @ColorInt int color) {
        final String stringText = textView.getText().toString();
        if (!stringText.isEmpty() && stringText.toLowerCase(Locale.getDefault()).contains(query)) {
            final int startPos = stringText.toLowerCase(Locale.getDefault()).indexOf(query);
            final int endPos = startPos + query.length();

            Spannable spanText = Spannable.Factory.getInstance().newSpannable(stringText);
            spanText.setSpan(new ForegroundColorSpan(color), startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(spanText, TextView.BufferType.SPANNABLE);
        }
    }

    /**
     * Highlight the query text within a TextView. Suitable for notifying user about the
     * searched query found in the adapter. TextView should not be empty. Please set your
     * default text first then, highlight the query text by using this function.
     *
     * @param query String to be highlighted.
     * @param textView to set the highlighted text.
     * @param colorId Color id of the highlighted text.
     *
     * @see {@link android.text.Spannable}.
     */
    public static void highlightQueryTextColorRes(@NonNull String query, @NonNull TextView textView,
            @ColorRes int colorId) {
        final String stringText = textView.getText().toString();
        if (!stringText.isEmpty() && stringText.toLowerCase(Locale.getDefault()).contains(query)) {
            final int startPos = stringText.toLowerCase(Locale.getDefault()).indexOf(query);
            final int endPos = startPos + query.length();

            Spannable spanText = Spannable.Factory.getInstance().newSpannable(stringText);
            spanText.setSpan(new ForegroundColorSpan(ContextCompat.getColor(textView.getContext(), colorId)),
                    startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(spanText, TextView.BufferType.SPANNABLE);
        }
    }

    /**
     * Create a scaled drawable to use it properly as dialog icon.
     * It will also remove the issue of too big dialog icon.
     *
     * @param context to retrieve resources.
     * @param drawable to scale.
     *
     * @return Scaled drawable to use as dialog icon.
     */
    public static Drawable createDialogIcon(Context context, @NonNull Drawable drawable) {
        int widthHeight = context.getResources().getDimensionPixelSize(R.dimen.sas_dialog_icon_size);
        return new ScaleDrawable(drawable, 0, widthHeight, widthHeight).getDrawable();
    }

    /**
     * Create a scaled drawable to use it properly as dialog icon.
     * It will also remove the issue of too big dialog icon.
     *
     * @param context to retrieve resources.
     * @param drawableId to scale.
     *
     * @return Scaled drawable to use as dialog icon.
     */
    public static Drawable createDialogIcon(Context context, @DrawableRes int drawableId) {
        return createDialogIcon(context, ContextCompat.getDrawable(context, drawableId));
    }
}