com.bilibili.magicasakura.utils.GradientDrawableInflateImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.bilibili.magicasakura.utils.GradientDrawableInflateImpl.java

Source

/*
 * Copyright (C) 2016 Bilibili
 *
 * 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.bilibili.magicasakura.utils;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.reflect.Field;

/**
 * @author xyczero617@gmail.com
 * @time 16/2/22
 */
class GradientDrawableInflateImpl implements DrawableInflateDelegate {
    private static Field sPaddingField;
    private static Field sStPaddingField;
    private static Field sStGradientPositions;
    private static Field sStGradientAngle;

    @Override
    public Drawable inflateDrawable(Context context, XmlPullParser parser, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        GradientDrawable gradientDrawable = new GradientDrawable();
        inflateGradientRootElement(context, attrs, gradientDrawable);

        int type;
        final int innerDepth = parser.getDepth() + 1;
        int depth;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            if (depth > innerDepth) {
                continue;
            }

            String name = parser.getName();

            if (name.equals("size")) {
                final int width = DrawableUtils.getAttrDimensionPixelSize(context, attrs, android.R.attr.width);
                final int height = DrawableUtils.getAttrDimensionPixelSize(context, attrs, android.R.attr.height);
                gradientDrawable.setSize(width, height);
            } else if (name.equals("gradient")
                    && Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                final float centerX = getAttrFloatOrFraction(context, attrs, android.R.attr.centerX, 0.5f, 1.0f,
                        1.0f);
                final float centerY = getAttrFloatOrFraction(context, attrs, android.R.attr.centerY, 0.5f, 1.0f,
                        1.0f);
                gradientDrawable.setGradientCenter(centerX, centerY);
                final boolean useLevel = DrawableUtils.getAttrBoolean(context, attrs, android.R.attr.useLevel,
                        false);
                gradientDrawable.setUseLevel(useLevel);
                final int gradientType = DrawableUtils.getAttrInt(context, attrs, android.R.attr.type, 0);
                gradientDrawable.setGradientType(gradientType);
                final int startColor = DrawableUtils.getAttrColor(context, attrs, android.R.attr.startColor,
                        Color.TRANSPARENT);
                final int centerColor = DrawableUtils.getAttrColor(context, attrs, android.R.attr.centerColor,
                        Color.TRANSPARENT);
                final int endColor = DrawableUtils.getAttrColor(context, attrs, android.R.attr.endColor,
                        Color.TRANSPARENT);
                if (!DrawableUtils.getAttrHasValue(context, attrs, android.R.attr.centerColor)) {
                    gradientDrawable.setColors(new int[] { startColor, endColor });
                } else {
                    gradientDrawable.setColors(new int[] { startColor, centerColor, endColor });
                    setStGradientPositions(gradientDrawable.getConstantState(), 0.0f,
                            centerX != 0.5f ? centerX : centerY, 1f);
                }

                if (gradientType == GradientDrawable.LINEAR_GRADIENT) {
                    int angle = (int) DrawableUtils.getAttrFloat(context, attrs, android.R.attr.angle, 0.0f);
                    angle %= 360;

                    if (angle % 45 != 0) {
                        throw new XmlPullParserException(
                                "<gradient> tag requires" + "'angle' attribute to " + "be a multiple of 45");
                    }

                    setStGradientAngle(gradientDrawable.getConstantState(), angle);

                    switch (angle) {
                    case 0:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
                        break;
                    case 45:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.BL_TR);
                        break;
                    case 90:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.BOTTOM_TOP);
                        break;
                    case 135:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.BR_TL);
                        break;
                    case 180:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.RIGHT_LEFT);
                        break;
                    case 225:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.TR_BL);
                        break;
                    case 270:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);
                        break;
                    case 315:
                        gradientDrawable.setOrientation(GradientDrawable.Orientation.TL_BR);
                        break;
                    }
                } else {
                    setGradientRadius(context, attrs, gradientDrawable, gradientType);
                }
            } else if (name.equals("solid")) {
                int color = DrawableUtils.getAttrColor(context, attrs, android.R.attr.color, Color.TRANSPARENT);
                gradientDrawable.setColor(getAlphaColor(color,
                        DrawableUtils.getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f)));
            } else if (name.equals("stroke")) {
                final float alphaMod = DrawableUtils.getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f);
                final int strokeColor = DrawableUtils.getAttrColor(context, attrs, android.R.attr.color,
                        Color.TRANSPARENT);
                final int strokeWidth = DrawableUtils.getAttrDimensionPixelSize(context, attrs,
                        android.R.attr.width);
                final float dashWidth = DrawableUtils.getAttrDimension(context, attrs, android.R.attr.dashWidth);
                if (dashWidth != 0.0f) {
                    final float dashGap = DrawableUtils.getAttrDimension(context, attrs, android.R.attr.dashGap);
                    gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod), dashWidth,
                            dashGap);
                } else {
                    gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod));
                }
            } else if (name.equals("corners")) {
                final int radius = DrawableUtils.getAttrDimensionPixelSize(context, attrs, android.R.attr.radius);
                gradientDrawable.setCornerRadius(radius);

                final int topLeftRadius = DrawableUtils.getAttrDimensionPixelSize(context, attrs,
                        android.R.attr.topLeftRadius, radius);
                final int topRightRadius = DrawableUtils.getAttrDimensionPixelSize(context, attrs,
                        android.R.attr.topRightRadius, radius);
                final int bottomLeftRadius = DrawableUtils.getAttrDimensionPixelSize(context, attrs,
                        android.R.attr.bottomLeftRadius, radius);
                final int bottomRightRadius = DrawableUtils.getAttrDimensionPixelSize(context, attrs,
                        android.R.attr.bottomRightRadius, radius);
                if (topLeftRadius != radius || topRightRadius != radius || bottomLeftRadius != radius
                        || bottomRightRadius != radius) {
                    // The corner radii are specified in clockwise order (see Path.addRoundRect())
                    gradientDrawable.setCornerRadii(
                            new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
                                    bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius });
                }
            } else if (name.equals("padding")) {
                final int paddingLeft = DrawableUtils.getAttrDimensionPixelOffset(context, attrs,
                        android.R.attr.left);
                final int paddingTop = DrawableUtils.getAttrDimensionPixelOffset(context, attrs,
                        android.R.attr.top);
                final int paddingRight = DrawableUtils.getAttrDimensionPixelOffset(context, attrs,
                        android.R.attr.right);
                final int paddingBottom = DrawableUtils.getAttrDimensionPixelOffset(context, attrs,
                        android.R.attr.bottom);
                if (paddingLeft != 0 || paddingTop != 0 || paddingRight != 0 || paddingBottom != 0) {
                    final Rect pad = new Rect();
                    pad.set(paddingLeft, paddingTop, paddingRight, paddingBottom);
                    try {
                        if (sPaddingField == null) {
                            sPaddingField = GradientDrawable.class.getDeclaredField("mPadding");
                            sPaddingField.setAccessible(true);
                        }
                        sPaddingField.set(gradientDrawable, pad);
                        if (sStPaddingField == null) {
                            sStPaddingField = Class
                                    .forName("android.graphics.drawable.GradientDrawable$GradientState")
                                    .getDeclaredField("mPadding");
                            sStPaddingField.setAccessible(true);
                        }
                        sStPaddingField.set(gradientDrawable.getConstantState(), pad);
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                Log.w("drawable", "Bad element under <shape>: " + name);
            }
        }
        return gradientDrawable;
    }

    void inflateGradientRootElement(Context context, AttributeSet attrs, GradientDrawable gradientDrawable) {
        int shape = DrawableUtils.getAttrInt(context, attrs, android.R.attr.shape, GradientDrawable.RECTANGLE);
        gradientDrawable.setShape(shape);
        boolean dither = DrawableUtils.getAttrBoolean(context, attrs, android.R.attr.dither, false);
        gradientDrawable.setDither(dither);
    }

    void setGradientRadius(Context context, AttributeSet attrs, GradientDrawable drawable, int gradientType)
            throws XmlPullParserException {
        TypedArray a = DrawableUtils.obtainAttributes(context.getResources(), context.getTheme(), attrs,
                new int[] { android.R.attr.gradientRadius });
        TypedValue value = a.peekValue(0);
        if (value != null) {
            boolean radiusRel = value.type == TypedValue.TYPE_FRACTION;
            drawable.setGradientRadius(radiusRel ? value.getFraction(1.0f, 1.0f) : value.getFloat());
        } else if (gradientType == GradientDrawable.RADIAL_GRADIENT) {
            throw new XmlPullParserException(
                    "<gradient> tag requires 'gradientRadius' " + "attribute with radial type");
        }
        a.recycle();
    }

    void setStGradientAngle(Drawable.ConstantState constantState, int angle) {
        try {
            if (sStGradientAngle == null) {
                sStGradientAngle = Class.forName("android.graphics.drawable.GradientDrawable$GradientState")
                        .getDeclaredField("mAngle");
                sStGradientAngle.setAccessible(true);
            }
            sStGradientAngle.set(constantState, angle);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    void setStGradientPositions(Drawable.ConstantState constantState, float... positions) {
        try {
            if (sStGradientPositions == null) {
                sStGradientPositions = Class.forName("android.graphics.drawable.GradientDrawable$GradientState")
                        .getDeclaredField("mPositions");
                sStGradientPositions.setAccessible(true);
            }
            sStGradientPositions.set(constantState, positions);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    float getAttrFloatOrFraction(Context context, AttributeSet attrs, int attr, float defaultValue, float base,
            float pbase) {
        TypedArray a = DrawableUtils.obtainAttributes(context.getResources(), context.getTheme(), attrs,
                new int[] { attr });
        TypedValue tv = a.peekValue(0);
        float v = defaultValue;
        if (tv != null) {
            boolean isFraction = tv.type == TypedValue.TYPE_FRACTION;
            v = isFraction ? tv.getFraction(base, pbase) : tv.getFloat();
        }
        a.recycle();
        return v;
    }

    int getAlphaColor(int baseColor, float alpha) {
        return alpha != 1.0f ? ColorUtils.setAlphaComponent(baseColor, Math.round(Color.alpha(baseColor) * alpha))
                : baseColor;
    }
}