com.google.android.material.imageview.ShapeableImageView 输出找不到颜色值 Failed to inflate ColorStateLis...

在非 Material Design 主题(如 Theme.AppCompat.Light.NoActionBar)中使用 com.google.android.material.imageview.ShapeableImageView,如果不需要 strokeColor ,或者在外部 style 中统一定义 <item name="strokeColor">@null</item><item name="strokeColor">@android:color/transparent</item>,在类内部获取描边颜色时会输出一堆找不到颜色的日志:

Image decoding logging dropped!
Failed to inflate ColorStateList, leaving it to the framework
java.lang.UnsupportedOperationException: Failed to resolve attribute at index 0: TypedValue{t=0x2/d=0x7f04013c a=-1}, theme={InheritanceMap=[id=0x7f13024ecom.a.b.c.d:style/Theme.Common, id=0x7f13024ccom.a.b.c.d:style/Theme.AppCompat.Light.NoActionBar, id=0x7f130246com.a.b.c.d:style/Theme.AppCompat.Light, id=0x7f130053com.a.b.c.d:style/Base.Theme.AppCompat.Light, id=0x7f1300bbcom.a.b.c.d:style/Base.V28.Theme.AppCompat.Light, id=0x7f1300b8com.a.b.c.d:style/Base.V26.Theme.AppCompat.Light, id=0x7f1300b2com.a.b.c.d:style/Base.V23.Theme.AppCompat.Light, id=0x7f1300b0com.a.b.c.d:style/Base.V22.Theme.AppCompat.Light, id=0x7f1300a5com.a.b.c.d:style/Base.V21.Theme.AppCompat.Light, id=0x7f1300becom.a.b.c.d:style/Base.V7.Theme.AppCompat.Light, id=0x7f13015acom.a.b.c.d:style/Platform.AppCompat.Light, id=0x7f130165com.a.b.c.d:style/Platform.V25.AppCompat.Light, id=0x1030241android:style/Theme.Material.Light.NoActionBar, id=0x1030237android:style/Theme.Material.Light, id=0x103000candroid:style/Theme.Light, id=0x1030005android:style/Theme], Themes=[com.a.b.c.d:style/Theme.Common, forced, com.a.b.c.d:style/Theme.AppCompat.Empty, forced, android:style/Theme.DeviceDefault.Light.DarkActionBar, forced]}
    at android.content.res.TypedArray.getColor(TypedArray.java:536)
    at androidx.core.content.res.ColorStateListInflaterCompat.inflate(ColorStateListInflaterCompat.java:157)
    at androidx.core.content.res.ColorStateListInflaterCompat.createFromXmlInner(ColorStateListInflaterCompat.java:122)
    at androidx.core.content.res.ColorStateListInflaterCompat.createFromXml(ColorStateListInflaterCompat.java:102)
    at androidx.core.content.res.ResourcesCompat.inflateColorStateList(ResourcesCompat.java:259)
    at androidx.core.content.res.ResourcesCompat.getColorStateList(ResourcesCompat.java:234)
    at androidx.core.content.ContextCompat.getColorStateList(ContextCompat.java:516)
    at androidx.appcompat.content.res.AppCompatResources.getColorStateList(AppCompatResources.java:46)

搜索了一些文章,所说的将 strokeColor 设置为 @null 其实是无效的,本地改为透明的也无效,依然输出一堆日志。
为解决不输出上诉日志,有两种方案,第一是用 Material Design 相关的主题,比如 Theme.MaterialComponents.Light.NoActionBar 等 Theme.MaterialComponents 的子主题;第二种方案是自己搞个类似的视图。
本文解决方法为直接把该类拷出来,把获取 strokeColor 的地方改一下即可。
下面贴出相关类,直接拿去用:

package com.a.b.c.d;

import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;

import com.google.android.material.R;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.android.material.shape.ShapeAppearancePathProvider;
import com.google.android.material.shape.Shapeable;

import java.lang.reflect.InvocationTargetException;

/**
 * 本类为实现在非 Material Design 主题中使用 ShapeableImageView ,基于 ShapeableImageView 修改获取描边颜色,避免输出一堆找不到颜色的日志
 *
 * @see com.google.android.material.imageview.ShapeableImageView 是AppCompatImageView 的一个扩展,专门用于应用自定义形状(如圆角、切角)来显示图像,该类需要配合支持 Material Design 的主题使用,否则 {@code com.google.android.material.resources.MaterialResources.getColorStateList()} 会输出找不到颜色的日志
 * <p>
 * An ImageView that draws the bitmap with the provided Shape.
 */
public class ShapeableImageView2 extends AppCompatImageView implements Shapeable {

    private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_ShapeableImageView;

    private static final int UNDEFINED_PADDING = Integer.MIN_VALUE;

    //    改动 一
//    private final ShapeAppearancePathProvider pathProvider = ShapeAppearancePathProvider.getInstance();
    private final ShapeAppearancePathProvider pathProvider =
            (ShapeAppearancePathProvider) Class.forName("com.google.android.material.shape.ShapeAppearancePathProvider").getMethod("getInstance").invoke(null);
    private final RectF destination;
    private final RectF maskRect;
    private final Paint borderPaint;
    private final Paint clearPaint;
    private final Path path = new Path();

    @Nullable
    private ColorStateList strokeColor;
    @Nullable
    private MaterialShapeDrawable shadowDrawable;

    private ShapeAppearanceModel shapeAppearanceModel;
    @Dimension
    private float strokeWidth;
    private Path maskPath;

    @Dimension
    private int leftContentPadding;
    @Dimension
    private int topContentPadding;
    @Dimension
    private int rightContentPadding;
    @Dimension
    private int bottomContentPadding;
    @Dimension
    private int startContentPadding;
    @Dimension
    private int endContentPadding;
    private boolean hasAdjustedPaddingAfterLayoutDirectionResolved = false;

    public ShapeableImageView2(Context context) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        this(context, null, 0);
    }

    public ShapeableImageView2(Context context, @Nullable AttributeSet attrs) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        this(context, attrs, 0);
    }

    public ShapeableImageView2(Context context, @Nullable AttributeSet attrs, int defStyle) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        super(wrap(context, attrs, defStyle, DEF_STYLE_RES), attrs, defStyle);
        // Ensure we are using the correctly themed context rather than the context that was passed in.
        context = getContext();

        clearPaint = new Paint();
        clearPaint.setAntiAlias(true);
        clearPaint.setColor(Color.WHITE);
        clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
        destination = new RectF();
        maskRect = new RectF();
        maskPath = new Path();
        TypedArray attributes =
                context.obtainStyledAttributes(
                        attrs, R.styleable.ShapeableImageView, defStyle, DEF_STYLE_RES);

        setLayerType(LAYER_TYPE_HARDWARE, null);

//         改动 二
//        MaterialResources.getColorStateList(
//                context, attributes, R.styleable.ShapeableImageView_strokeColor);
        strokeColor = attributes.getColorStateList(R.styleable.ShapeableImageView_strokeColor);

        strokeWidth = attributes.getDimensionPixelSize(R.styleable.ShapeableImageView_strokeWidth, 0);

        // First set all 4 contentPadding values from the `app:contentPadding` attribute:
        int contentPadding = attributes
                .getDimensionPixelSize(R.styleable.ShapeableImageView_contentPadding, 0);
        leftContentPadding = contentPadding;
        topContentPadding = contentPadding;
        rightContentPadding = contentPadding;
        bottomContentPadding = contentPadding;

        // Update each contentPadding value individually from the `app:contentPadding<Side>`
        leftContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingLeft, contentPadding);
        topContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingTop, contentPadding);
        rightContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingRight, contentPadding);
        bottomContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingBottom, contentPadding);

        // Update the relative start and end contentPadding values from those attributes:
        startContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingStart, UNDEFINED_PADDING);
        endContentPadding = attributes.getDimensionPixelSize(
                R.styleable.ShapeableImageView_contentPaddingEnd, UNDEFINED_PADDING);

        attributes.recycle();

        borderPaint = new Paint();
        borderPaint.setStyle(Style.STROKE);
        borderPaint.setAntiAlias(true);
        shapeAppearanceModel =
                ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            setOutlineProvider(new ShapeableImageView2.OutlineProvider());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
            return;
        }

        if (VERSION.SDK_INT > 19 && !isLayoutDirectionResolved()) {
            return;
        }

        hasAdjustedPaddingAfterLayoutDirectionResolved = true;

        // Update the super padding to be the combined `android:padding` and
        // `app:contentPadding`, keeping with ShapeableImageView2's internal padding contract:
        if (VERSION.SDK_INT >= 21 && (isPaddingRelative() || isContentPaddingRelative())) {
            setPaddingRelative(
                    super.getPaddingStart(),
                    super.getPaddingTop(),
                    super.getPaddingEnd(),
                    super.getPaddingBottom());
            return;
        }

        setPadding(
                super.getPaddingLeft(),
                super.getPaddingTop(),
                super.getPaddingRight(),
                super.getPaddingBottom());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(maskPath, clearPaint);
        drawStroke(canvas);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        updateShapeMask(width, height);
    }

    /**
     * Set additional padding on the image that is not applied to the background.
     *
     * @param left   the padding on the left of the image in pixels
     * @param top    the padding on the top of the image in pixels
     * @param right  the padding on the right of the image in pixels
     * @param bottom the padding on the bottom of the image in pixels
     */
    public void setContentPadding(
            @Dimension int left, @Dimension int top, @Dimension int right, @Dimension int bottom) {
        startContentPadding = UNDEFINED_PADDING;
        endContentPadding = UNDEFINED_PADDING;

        // Super padding is equal to background padding + content padding. Adjust the content padding
        //  portion of the super padding here:
        super.setPadding(
                super.getPaddingLeft() - leftContentPadding + left,
                super.getPaddingTop() - topContentPadding + top,
                super.getPaddingRight() - rightContentPadding + right,
                super.getPaddingBottom() - bottomContentPadding + bottom);

        leftContentPadding = left;
        topContentPadding = top;
        rightContentPadding = right;
        bottomContentPadding = bottom;
    }

    /**
     * Set additional relative padding on the image that is not applied to the background.
     *
     * @param start  the padding on the start of the image in pixels
     * @param top    the padding on the top of the image in pixels
     * @param end    the padding on the end of the image in pixels
     * @param bottom the padding on the bottom of the image in pixels
     */
    @RequiresApi(17)
    public void setContentPaddingRelative(
            @Dimension int start, @Dimension int top, @Dimension int end, @Dimension int bottom) {
        // Super padding is equal to background padding + content padding. Adjust the content padding
        //  portion of the super padding here:
        super.setPaddingRelative(
                super.getPaddingStart() - getContentPaddingStart() + start,
                super.getPaddingTop() - topContentPadding + top,
                super.getPaddingEnd() - getContentPaddingEnd() + end,
                super.getPaddingBottom() - bottomContentPadding + bottom);

        leftContentPadding = isRtl() ? end : start;
        topContentPadding = top;
        rightContentPadding = isRtl() ? start : end;
        bottomContentPadding = bottom;
    }

    private boolean isContentPaddingRelative() {
        return startContentPadding != UNDEFINED_PADDING || endContentPadding != UNDEFINED_PADDING;
    }

    /**
     * The additional padding on the bottom of the image, which is not applied to the background.
     *
     * @return the bottom padding on the image
     */
    @Dimension
    public int getContentPaddingBottom() {
        return bottomContentPadding;
    }

    /**
     * The additional relative padding on the end of the image, which is not applied to the
     * background.
     *
     * @return the end padding on the image
     */
    @Dimension
    public final int getContentPaddingEnd() {
        if (endContentPadding != UNDEFINED_PADDING) {
            return endContentPadding;
        } else {
            return isRtl() ? leftContentPadding : rightContentPadding;
        }
    }

    /**
     * The additional padding on the left of the image, which is not applied to the background.
     *
     * @return the left padding on the image
     */
    @Dimension
    public int getContentPaddingLeft() {
        if (isContentPaddingRelative()) {
            if (isRtl() && endContentPadding != UNDEFINED_PADDING) {
                return endContentPadding;
            } else if (!isRtl() && startContentPadding != UNDEFINED_PADDING) {
                return startContentPadding;
            }
        }

        return leftContentPadding;
    }

    /**
     * The additional padding on the right of the image, which is not applied to the background.
     *
     * @return the right padding on the image
     */
    @Dimension
    public int getContentPaddingRight() {
        if (isContentPaddingRelative()) {
            if (isRtl() && startContentPadding != UNDEFINED_PADDING) {
                return startContentPadding;
            } else if (!isRtl() && endContentPadding != UNDEFINED_PADDING) {
                return endContentPadding;
            }
        }

        return rightContentPadding;
    }

    /**
     * The additional relative padding on the start of the image, which is not applied to the
     * background.
     *
     * @return the start padding on the image
     */
    @Dimension
    public final int getContentPaddingStart() {
        if (startContentPadding != UNDEFINED_PADDING) {
            return startContentPadding;
        } else {
            return isRtl() ? rightContentPadding : leftContentPadding;
        }
    }

    /**
     * The additional padding on the top of the image, which is not applied to the background.
     *
     * @return the top padding on the image
     */
    @Dimension
    public int getContentPaddingTop() {
        return topContentPadding;
    }

    private boolean isRtl() {
        return VERSION.SDK_INT >= 17 && getLayoutDirection() == LAYOUT_DIRECTION_RTL;
    }

    /**
     * Set the padding. This is applied to both the background and the image, and does not affect the
     * content padding differentiating the image from the background.
     *
     * @param left   the left padding in pixels
     * @param top    the top padding in pixels
     * @param right  the right padding in pixels
     * @param bottom the bottom padding in pixels
     */
    @Override
    public void setPadding(
            @Dimension int left, @Dimension int top, @Dimension int right, @Dimension int bottom) {
        super.setPadding(
                left + getContentPaddingLeft(),
                top + getContentPaddingTop(),
                right + getContentPaddingRight(),
                bottom + getContentPaddingBottom());
    }

    /**
     * Set the relative padding. This is applied to both the background and the image, and does not
     * affect the content padding differentiating the image from the background.
     *
     * @param start  the start padding in pixels
     * @param top    the top padding in pixels
     * @param end    the end padding in pixels
     * @param bottom the bottom padding in pixels
     */
    @Override
    public void setPaddingRelative(
            @Dimension int start, @Dimension int top, @Dimension int end, @Dimension int bottom) {
        super.setPaddingRelative(
                start + getContentPaddingStart(),
                top + getContentPaddingTop(),
                end + getContentPaddingEnd(),
                bottom + getContentPaddingBottom());
    }

    /**
     * The padding on the bottom of the View, applied to both the image and the background.
     *
     * @return the bottom padding
     */
    @Override
    @Dimension
    public int getPaddingBottom() {
        return super.getPaddingBottom() - getContentPaddingBottom();
    }

    /**
     * The relative padding on the end of the View, applied to both the image and the background.
     *
     * @return the end padding
     */
    @Override
    @Dimension
    public int getPaddingEnd() {
        return super.getPaddingEnd() - getContentPaddingEnd();
    }

    /**
     * The padding on the left of the View, applied to both the image and the background.
     *
     * @return the left padding
     */
    @Override
    @Dimension
    public int getPaddingLeft() {
        return super.getPaddingLeft() - getContentPaddingLeft();
    }

    /**
     * The padding on the right of the View, applied to both the image and the background.
     *
     * @return the right padding
     */
    @Override
    @Dimension
    public int getPaddingRight() {
        return super.getPaddingRight() - getContentPaddingRight();
    }

    /**
     * The relative padding on the start of the View, applied to both the image and the background.
     *
     * @return the start padding
     */
    @Override
    @Dimension
    public int getPaddingStart() {
        return super.getPaddingStart() - getContentPaddingStart();
    }

    /**
     * The padding on the top of the View, applied to both the image and the background.
     *
     * @return the top padding
     */
    @Override
    @Dimension
    public int getPaddingTop() {
        return super.getPaddingTop() - getContentPaddingTop();
    }

    @Override
    public void setShapeAppearanceModel(@NonNull ShapeAppearanceModel shapeAppearanceModel) {
        this.shapeAppearanceModel = shapeAppearanceModel;
        if (shadowDrawable != null) {
            shadowDrawable.setShapeAppearanceModel(shapeAppearanceModel);
        }
        updateShapeMask(getWidth(), getHeight());
        invalidate();
        if (VERSION.SDK_INT >= 21) {
            invalidateOutline();
        }
    }

    @NonNull
    @Override
    public ShapeAppearanceModel getShapeAppearanceModel() {
        return shapeAppearanceModel;
    }

    private void updateShapeMask(int width, int height) {
        destination.set(
                getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());
        pathProvider.calculatePath(shapeAppearanceModel, 1f /*interpolation*/, destination, path);
        // Remove path from rect to draw with clear paint.
        maskPath.rewind();
        maskPath.addPath(path);
        // Do not include padding to clip the background too.
        maskRect.set(0, 0, width, height);
        maskPath.addRect(maskRect, Direction.CCW);
    }

    private void drawStroke(Canvas canvas) {
        if (strokeColor == null) {
            return;
        }

        borderPaint.setStrokeWidth(strokeWidth);
        int colorForState =
                strokeColor.getColorForState(getDrawableState(), strokeColor.getDefaultColor());

        if (strokeWidth > 0 && colorForState != Color.TRANSPARENT) {
            borderPaint.setColor(colorForState);
            canvas.drawPath(path, borderPaint);
        }
    }

    /**
     * Sets the stroke color resource for this ImageView. Both stroke color and stroke width must be
     * set for a stroke to be drawn.
     *
     * @param strokeColorResourceId Color resource to use for the stroke.
     * @attr ref com.google.android.material.R.styleable#ShapeableImageView_strokeColor
     * @see #setStrokeColor(ColorStateList)
     * @see #getStrokeColor()
     */
    public void setStrokeColorResource(@ColorRes int strokeColorResourceId) {
        setStrokeColor(AppCompatResources.getColorStateList(getContext(), strokeColorResourceId));
    }

    /**
     * Returns the stroke color for this ImageView.
     *
     * @attr ref com.google.android.material.R.styleable#ShapeableImageView_strokeColor
     * @see #setStrokeColor(ColorStateList)
     * @see #setStrokeColorResource(int)
     */
    @Nullable
    public ColorStateList getStrokeColor() {
        return strokeColor;
    }

    /**
     * Sets the stroke width for this ImageView. Both stroke color and stroke width must be set for a
     * stroke to be drawn.
     *
     * @param strokeWidth Stroke width for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableImageView_strokeWidth
     * @see #setStrokeWidthResource(int)
     * @see #getStrokeWidth()
     */
    public void setStrokeWidth(@Dimension float strokeWidth) {
        if (this.strokeWidth != strokeWidth) {
            this.strokeWidth = strokeWidth;
            invalidate();
        }
    }

    /**
     * Sets the stroke width dimension resource for this ImageView. Both stroke color and stroke width
     * must be set for a stroke to be drawn.
     *
     * @param strokeWidthResourceId Stroke width dimension resource for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableImageView_strokeWidth
     * @see #setStrokeWidth(float)
     * @see #getStrokeWidth()
     */
    public void setStrokeWidthResource(@DimenRes int strokeWidthResourceId) {
        setStrokeWidth(getResources().getDimensionPixelSize(strokeWidthResourceId));
    }

    /**
     * Gets the stroke width for this ImageView.
     *
     * @return Stroke width for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableImageView_strokeWidth
     * @see #setStrokeWidth(float)
     * @see #setStrokeWidthResource(int)
     */
    @Dimension
    public float getStrokeWidth() {
        return strokeWidth;
    }

    public void setStrokeColor(@Nullable ColorStateList strokeColor) {
        this.strokeColor = strokeColor;
        invalidate();
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    class OutlineProvider extends ViewOutlineProvider {

        private final Rect rect = new Rect();

        @Override
        public void getOutline(View view, Outline outline) {
            if (shapeAppearanceModel == null) {
                return;
            }

            if (shadowDrawable == null) {
                shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
            }

            destination.round(rect);
            shadowDrawable.setBounds(rect);
            shadowDrawable.getOutline(outline);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容