Android 动态设置Shape

引言:之前涉及到设置view背景的地方几乎都是通过写<shape>标签的方式实现的。慢慢的,项目里的xml越来越多,命名都成问题了!于是就想用动态设置shape的方式来替换静态配置shape标签。

静态配置shape

这个不多说了,梯子备好了,自行前往!
https://developer.android.google.cn/guide/topics/resources/drawable-resource.html#Shape
里面有非常详细的介绍。包括shape 等标签参数介绍、使用规则、映射的Drawaable。
Android 动态设置shape
Android shape标签对应的对象

这里对形状可绘制对象的描述感觉有点出入,写的是创建ShapeDrawable,但是进入对应的条目后发现是GradientDrawable.


动态设置shape

想要动态配置,首先需要知道在xml中写的<shape><selector><level-list> 等标签的映射对象是什么。这里有个插曲,最开始我也以为<shape>标签对应的就是ShapeDrawable呢,写的时候发现没法描边(Stroke), 试了两种方案 1.通过设置描边画笔,给paint设置宽度和颜色;2.用LevelListDrawable 通过设置多个层一个填充层,一个描边层来组合。结果这两种方法都不行,后来才去翻的源码。从view.setBackgroundResource()开始追。

Resources.java

  public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            // 从这里开始加载Drawable
            return impl.loadDrawable(this, value, id, theme, true);
        } finally {
            releaseTempTypedValue(value);
        }
    }

ResourcesImpl.java

  @Nullable
  Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
            boolean useCache) throws NotFoundException {
        try {
            ......

            final Drawable.ConstantState cs;
            if (isColorDrawable) {
                cs = sPreloadedColorDrawables.get(key);
            } else {
                cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
            }

            Drawable dr;
            if (cs != null) {
                dr = cs.newDrawable(wrapper);
            } else if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            } else {
                // 从xml或者资源流中加载Drawable
                dr = loadDrawableForCookie(wrapper, value, id, null);
            }
            return dr;
        } catch (Exception e) {
            ......
        }
    }
    /**
     * Loads a drawable from XML or resources stream.
     * 从xml或者数据流中加载Drawable
     */
    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme) {

        final String file = value.string.toString();
        ......

        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
            // ok,我们看下这里,通过解析器把xml文件解析成Drawable对象
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(wrapper, rp, theme);
                rp.close();
            } else {
            // 流的解析
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
            ......
        }
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

        return dr;
    }

Drawable.java

    // 使用可选的theme从XML文档中创建drawable对象。
    public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme)
            throws XmlPullParserException, IOException {
        AttributeSet attrs = Xml.asAttributeSet(parser);
        ......
        // 从xml内部以指定主题创建一个Drawable对象
        Drawable drawable = createFromXmlInner(r, parser, attrs, theme);

        if (drawable == null) {
            throw new RuntimeException("Unknown initial tag: " + parser.getName());
        }

        return drawable;
    }
    // 从xml内部以指定主题创建一个Drawable对象
    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
            Theme theme) throws XmlPullParserException, IOException {
        return r.getDrawableInflater().inflateFromXml(parser.getName(), parser, attrs, theme);
    }

追到这里发现是从Resources 中调用了一个方法。在Resources.java 中搜索getDrawableInflater()这个方法,出现了DrawableInflater这个类。
Resources.java

    // 该inflater用于创建Drawable对象
    public final DrawableInflater getDrawableInflater() {
        if (mDrawableInflater == null) {
            mDrawableInflater = new DrawableInflater(this, mClassLoader);
        }
        return mDrawableInflater;
    }

没法直接追下去了,既然这样我们在源码中直接搜索DrawableInflater.java这个类,然后查看它里面的inflateFromXml() 方法。(注:我这里的源码版本是android-25)
DrawableInflater.java

    @NonNull
    @SuppressWarnings("deprecation")
    private Drawable inflateFromTag(@NonNull String name) {
        switch (name) {
            case "selector":
                return new StateListDrawable();
            case "animated-selector":
                return new AnimatedStateListDrawable();
            case "level-list":
                return new LevelListDrawable();
            case "layer-list":
                return new LayerDrawable();
            case "transition":
                return new TransitionDrawable();
            case "ripple":
                return new RippleDrawable();
            case "color":
                return new ColorDrawable();
            case "shape":
                return new GradientDrawable();
            case "vector":
                return new VectorDrawable();
            case "animated-vector":
                return new AnimatedVectorDrawable();
            case "scale":
                return new ScaleDrawable();
            case "clip":
                return new ClipDrawable();
            case "rotate":
                return new RotateDrawable();
            case "animated-rotate":
                return new AnimatedRotateDrawable();
            case "animation-list":
                return new AnimationDrawable();
            case "inset":
                return new InsetDrawable();
            case "bitmap":
                return new BitmapDrawable();
            case "nine-patch":
                return new NinePatchDrawable();
            default:
                return null;
        }
    }

到这里总该明白与xml中的标签(shape等标签)相对应的对象是哪些了吧。项目中用的最多的就是圆角矩形背景了,然后有的会有描边,还有选择器的效果。我们就用GradientDrawable 来实现。GradientDrawable可以用来设置shape类型、shape填充色、描边色和矩形的边角弧度。


动态设置shape代码

以圆角矩形为例:
android 动态设置shape

    /**
     * 获得一个指定填充色,边框宽度、颜色的圆角矩形drawable。
     * Android 中 在xml中写的"shape"标签映射对象就是GradientDrawable。
     * 通过设置solidColors 和strokeColors 可实现选择器的效果
     *
     * @param solidColors  填充色
     * @param strokeColors 描边色
     * @param strokeWidth  描边线宽度
     * @param dashWidth    虚线(破折线)的长度(以像素为单位)
     * @param dashGap      虚线(破折线)间距,当dashGap=0dp时,为实线
     * @param radius       圆角角度
     * @return GradientDrawable
     */
    public static Drawable getShapeDrawable(ColorStateList solidColors,
                                            ColorStateList strokeColors, int strokeWidth, 
                                            float dashWidth, float dashGap,
                                            float radius) {
        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
        gradientDrawable.setCornerRadius(radius);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            gradientDrawable.setColor(solidColors);
            //显示一条虚线,破折线的宽度为dashWith,破折线之间的空隙的宽度为dashGap,当dashGap=0dp时,为实线
            gradientDrawable.setStroke(strokeWidth, strokeColors, dashWidth, dashGap);
        } else {
            gradientDrawable.setColor(solidColors.getDefaultColor());
            //显示一条虚线,破折线的宽度为dashWith,破折线之间的空隙的宽度为dashGap,当dashGap=0dp时,为实线
            gradientDrawable.setStroke(strokeWidth, strokeColors.getDefaultColor(), dashWidth, dashGap);
        }
        return gradientDrawable;
    }
  /**
     * 获得一个指定填充色,指定描边色的圆角矩形drawable
     *
     * @param solidColor  填充色
     * @param strokeColor 描边色
     * @param strokeWidth 描边线宽度
     * @param dashWidth   虚线(破折线)宽度
     * @param dashGap     虚线(破折线)间距,当dashGap=0dp时,为实线
     * @param radius      圆角角度
     * @return GradientDrawable
     */
    public static Drawable getShapeDrawable(@ColorInt int solidColor,
                                            @ColorInt int strokeColor, int strokeWidth,
                                            float dashWidth, float dashGap,
                                            float radius) {
        return getShapeDrawable(ColorStateList.valueOf(solidColor),
                ColorStateList.valueOf(strokeColor), strokeWidth, dashWidth, dashGap,
                radius);
    }

    /**
     * 获得一个选择器Drawable.
     * Android 中 在xml中写的"selector"标签映射对象就是StateListDrawable 对象
     *
     * @param defaultDrawable 默认时显示的Drawable
     * @param pressedDrawable 按下时显示的Drawable
     * @return 选择器Drawable
     */
    public static StateListDrawable getSelectorDrawable(Drawable defaultDrawable, Drawable pressedDrawable) {
        if (defaultDrawable == null) return null;
        if (pressedDrawable == null) pressedDrawable = defaultDrawable;
        int[][] state = {{-android.R.attr.state_pressed}, {android.R.attr.state_pressed}};
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(state[0], defaultDrawable);
        stateListDrawable.addState(state[1], pressedDrawable);
        return stateListDrawable;
    }

    /**
     * 获得一个选择器Drawable.
     * Android 中 在xml中写的"selector"标签映射对象就是StateListDrawable 对象
     *
     * @param defaultColor 默认时显示的颜色
     * @param pressedColor 按下时显示的颜色
     * @return 选择器Drawable
     */
    public static StateListDrawable getSelectorDrawable(int defaultColor, int pressedColor, float radius) {

        Drawable defaultDrawable = getSolidShapeDrawable(defaultColor, radius);
        Drawable pressedDrawable = getSolidShapeDrawable(pressedColor, radius);

        return getSelectorDrawable(defaultDrawable, pressedDrawable);
    }

更多功能参考Drawable工具类:http://blog.csdn.net/JM_beizi/article/details/77433558

发布了22 篇原创文章 · 获赞 44 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/JM_beizi/article/details/77531042