public class

DrawableUtils

extends java.lang.Object

 java.lang.Object

↳androidx.appcompat.widget.DrawableUtils

Gradle dependencies

compile group: 'androidx.appcompat', name: 'appcompat-resources', version: '1.7.0'

  • groupId: androidx.appcompat
  • artifactId: appcompat-resources
  • version: 1.7.0

Artifact androidx.appcompat:appcompat-resources:1.7.0 it located at Google repository (https://maven.google.com/)

Androidx class mapping:

androidx.appcompat.widget.DrawableUtils android.support.v7.widget.DrawableUtils

Summary

Fields
public static final RectINSETS_NONE

Methods
public static booleancanSafelyMutateDrawable(Drawable drawable)

Some drawable implementations have problems with mutation.

public static RectgetOpticalBounds(Drawable drawable)

Allows us to get the optical insets for a Drawable.

public static PorterDuff.ModeparseTintMode(int value, PorterDuff.Mode defaultMode)

Parses tint mode.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final Rect INSETS_NONE

Methods

public static Rect getOpticalBounds(Drawable drawable)

Allows us to get the optical insets for a Drawable. Since this is hidden we need to use reflection. Since the Insets class is hidden also, we return a Rect instead.

public static boolean canSafelyMutateDrawable(Drawable drawable)

Deprecated: it is always true.

Some drawable implementations have problems with mutation. This method returns false if there is a known issue in the given drawable's implementation.

public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode)

Parses tint mode.

Source

/*
 * Copyright (C) 2014 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 androidx.appcompat.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;

import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Insets;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;

import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.drawable.DrawableCompat;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@RestrictTo(LIBRARY_GROUP_PREFIX)
public class DrawableUtils {

    private static final int[] CHECKED_STATE_SET = new int[] { android.R.attr.state_checked };
    private static final int[] EMPTY_STATE_SET = new int[0];

    public static final Rect INSETS_NONE = new Rect();

    private DrawableUtils() {
        // This class is non-instantiable.
    }

    /**
     * Allows us to get the optical insets for a {@link Drawable}. Since this is hidden we need to
     * use reflection. Since the {@code Insets} class is hidden also, we return a Rect instead.
     */
    @NonNull
    public static Rect getOpticalBounds(@NonNull Drawable drawable) {
        if (Build.VERSION.SDK_INT >= 29) {
            final Insets insets = Api29Impl.getOpticalInsets(drawable);
            return new Rect(
                    insets.left,
                    insets.top,
                    insets.right,
                    insets.bottom
            );
        } else {
            return Api18Impl.getOpticalInsets(DrawableCompat.unwrap(drawable));
        }
    }

    /**
     * Attempt the fix any issues in the given drawable, usually caused by platform bugs in the
     * implementation. This method should be call after retrieval from
     * {@link Resources} or a {@link TypedArray}.
     */
    static void fixDrawable(@NonNull Drawable drawable) {
        String className = drawable.getClass().getName();
        if (Build.VERSION.SDK_INT == 21
                && "android.graphics.drawable.VectorDrawable".equals(className)) {
            // VectorDrawable has an issue on API 21 where it sometimes doesn't create its tint
            // filter until a state change event has occurred.
            forceDrawableStateChange(drawable);
        } else if (Build.VERSION.SDK_INT >= 29 && Build.VERSION.SDK_INT < 31
                && "android.graphics.drawable.ColorStateListDrawable".equals(className)) {
            // ColorStateListDrawable has an issue on APIs 29 and 30 where it doesn't set up the
            // default color until a state change event has occurred.
            forceDrawableStateChange(drawable);
        }
    }

    /**
     * Some drawable implementations have problems with mutation. This method returns false if
     * there is a known issue in the given drawable's implementation.
     *
     * @deprecated it is always true.
     */
    @Deprecated
    public static boolean canSafelyMutateDrawable(@NonNull Drawable drawable) {
        return true;
    }

    /**
     * Force a drawable state change.
     */
    private static void forceDrawableStateChange(final Drawable drawable) {
        final int[] originalState = drawable.getState();
        if (originalState == null || originalState.length == 0) {
            // The drawable doesn't have a state, so set it to be checked
            drawable.setState(CHECKED_STATE_SET);
        } else {
            // Else the drawable does have a state, so clear it
            drawable.setState(EMPTY_STATE_SET);
        }
        // Now set the original state
        drawable.setState(originalState);
    }

    /**
     * Parses tint mode.
     */
    public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
        switch (value) {
            case 3:
                return PorterDuff.Mode.SRC_OVER;
            case 5:
                return PorterDuff.Mode.SRC_IN;
            case 9:
                return PorterDuff.Mode.SRC_ATOP;
            case 14:
                return PorterDuff.Mode.MULTIPLY;
            case 15:
                return PorterDuff.Mode.SCREEN;
            case 16:
                return PorterDuff.Mode.ADD;
            default:
                return defaultMode;
        }
    }

    // Only accessible on SDK_INT < 29.
    static class Api18Impl {
        private static final boolean sReflectionSuccessful;
        private static final Method sGetOpticalInsets;
        private static final Field sLeft;
        private static final Field sTop;
        private static final Field sRight;
        private static final Field sBottom;

        static {
            Method getOpticalInsets = null;
            Field left = null;
            Field top = null;
            Field right = null;
            Field bottom = null;
            boolean success = false;

            try {
                Class<?> insets = Class.forName("android.graphics.Insets");
                getOpticalInsets = Drawable.class.getMethod("getOpticalInsets");
                left = insets.getField("left");
                top = insets.getField("top");
                right = insets.getField("right");
                bottom = insets.getField("bottom");
                success = true;
            } catch (NoSuchMethodException e) {
                // Not successful, null everything out.
            } catch (ClassNotFoundException e) {
                // Not successful, null everything out.
            } catch (NoSuchFieldException e) {
                // Not successful, null everything out.
            }

            if (success) {
                sGetOpticalInsets = getOpticalInsets;
                sLeft = left;
                sTop = top;
                sRight = right;
                sBottom = bottom;
                sReflectionSuccessful = true;
            } else {
                sGetOpticalInsets = null;
                sLeft = null;
                sTop = null;
                sRight = null;
                sBottom = null;
                sReflectionSuccessful = false;
            }
        }

        private Api18Impl() {
            // This class is not instantiable.
        }

        @NonNull
        static Rect getOpticalInsets(@NonNull Drawable drawable) {
            // Check the SDK_INT to avoid UncheckedReflection error.
            if (Build.VERSION.SDK_INT < 29 && sReflectionSuccessful) {
                try {
                    Object insets = sGetOpticalInsets.invoke(drawable);
                    if (insets != null) {
                        return new Rect(
                                sLeft.getInt(insets),
                                sTop.getInt(insets),
                                sRight.getInt(insets),
                                sBottom.getInt(insets)
                        );
                    }
                } catch (IllegalAccessException e) {
                    // Ignore, we'll return empty insets.
                } catch (InvocationTargetException e) {
                    // Ignore, we'll return empty insets.
                }
            }
            return DrawableUtils.INSETS_NONE;
        }
    }

    @RequiresApi(29)
    static class Api29Impl {
        private Api29Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static Insets getOpticalInsets(Drawable drawable) {
            return drawable.getOpticalInsets();
        }
    }
}