在Android代码中创建环形

时间:2012-02-19 02:26:03

标签: android android-layout android-ui

我有以下形状XML:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:a="http://schemas.android.com/apk/res/android"
       a:shape="ring"
       a:innerRadiusRatio="3"
       a:thicknessRatio="8"
       a:useLevel="false">

    <!-- some other stuff goes here -->

    </gradient>
</shape>

我想使用代码来创建这个形状,因为有些东西需要在我做之前动态计算,所以静态预定义布局不会削减它。

我是Android的新手,无法弄清楚XML如何转换为代码,并且没有继承自Shape的RingShape类。

除了回答这个问题之外,如果有一个指南可以详细说明XML和Java代码之间的关系以及如何处理XML以便最终在屏幕上显示,我也会感谢链接。感谢。

5 个答案:

答案 0 :(得分:11)

鲁本已经指出了最有用的观察,所以我只关注故事的实施方面。使用反射的多种方法可能会为您提供所需的内容。

首先是(ab)使用带有GradientState引用的私有GradientDrawable构造函数。不幸的是,后者是包含可见性的最终子类,因此您无法轻松访问它。为了使用它,您需要进一步深入使用反射或将其功能模仿到您自己的代码中。

第二种方法是使用反射来获取私有成员变量mGradientState,幸运的是它具有getConstantState()形式的getter。这将为您提供ConstantState,它在运行时实际上是一个GradientState,因此我们可以使用反射来访问其成员并在运行时更改它们。

为了支持上述陈述,这里有一个基本的实现,可以从代码中创建一个环形的drawable:

<强> RingDrawable.java

public class RingDrawable extends GradientDrawable {

    private Class<?> mGradientState;

    public RingDrawable() {
        this(Orientation.TOP_BOTTOM, null);
    }

    public RingDrawable(int innerRadius, int thickness, float innerRadiusRatio, float thicknessRatio) {
        this(Orientation.TOP_BOTTOM, null, innerRadius, thickness, innerRadiusRatio, thicknessRatio);
    }

    public RingDrawable(GradientDrawable.Orientation orientation, int[] colors) {
        super(orientation, colors);
        setShape(RING);
    }

    public RingDrawable(GradientDrawable.Orientation orientation, int[] colors, int innerRadius, int thickness, float innerRadiusRatio, float thicknessRatio) {
        this(orientation, colors);
        try {
            setInnerRadius(innerRadius);
            setThickness(thickness);
            setInnerRadiusRatio(innerRadiusRatio);
            setThicknessRatio(thicknessRatio);
        } catch (Exception e) {
            // fail silently - change to your own liking
            e.printStackTrace();
        }
    }

    public void setInnerRadius(int radius) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        if (mGradientState == null) mGradientState = resolveGradientState();
        Field innerRadius = resolveField(mGradientState, "mInnerRadius");
        innerRadius.setInt(getConstantState(), radius);
    }       

    public void setThickness(int thicknessValue) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        if (mGradientState == null) mGradientState = resolveGradientState();
        Field thickness = resolveField(mGradientState, "mThickness");
        thickness.setInt(getConstantState(), thicknessValue);
    }

    public void setInnerRadiusRatio(float ratio) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        if (mGradientState == null) mGradientState = resolveGradientState();
        Field innerRadiusRatio = resolveField(mGradientState, "mInnerRadiusRatio");
        innerRadiusRatio.setFloat(getConstantState(), ratio);
    }

    public void setThicknessRatio(float ratio) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        if (mGradientState == null) mGradientState = resolveGradientState();
        Field thicknessRatio = resolveField(mGradientState, "mThicknessRatio");
        thicknessRatio.setFloat(getConstantState(), ratio);
    }

    private Class<?> resolveGradientState() {
        Class<?>[] classes = GradientDrawable.class.getDeclaredClasses();
        for (Class<?> singleClass : classes) {
            if (singleClass.getSimpleName().equals("GradientState")) return singleClass;
        }
        throw new RuntimeException("GradientState could not be found in current GradientDrawable implementation");
    }

    private Field resolveField(Class<?> source, String fieldName) throws SecurityException, NoSuchFieldException {
        Field field = source.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field;
    }

}

以上可以按如下方式用于从代码创建RingDrawable并将其显示在标准ImageView中。

ImageView target = (ImageView) findViewById(R.id.imageview);
RingDrawable ring = new RingDrawable(10, 20, 0, 0);
ring.setColor(Color.BLUE);
target.setImageDrawable(ring);

这将在ImageView中显示一个简单的不透明蓝色环(内部半径为10个单位,厚度为20个单位)。您需要确保不将ImageView的宽度和高度设置为wrap_content,除非您将ring.setSize(width, height)添加到上面的代码中以便显示它。

希望这能以任何方式帮助你。

答案 1 :(得分:10)

戒指和其他形状为GradientDrawables

如果你看一下GradientDrawable的the source code,你会发现某些属性(比如innerRadius)只能通过XML定义 ......它们不会通过访问器公开方法。相关状态对于类来说也是无用的,因此子类化也没有帮助。

答案 2 :(得分:3)

您可以这样做:

private ShapeDrawable newRingShapeDrawable(int color) {
        ShapeDrawable drawable = new ShapeDrawable(new OvalShape());
        drawable.getPaint().setColor(color);
        drawable.getPaint().setStrokeWidth(2);
        drawable.getPaint().setStyle(Paint.Style.STROKE);
        return drawable;
}

答案 3 :(得分:2)

可以从代码中执行此操作:

int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP); // this can be used to make it circle
float[] outerR = new float[]{r, r, r, r, r, r, r, r};
int border = dipToPixels(2); // border of circle
RectF rect = new RectF(border, border, border, border);
RoundRectShape rr = new RoundRectShape(outerR, rect, outerR);// must checkout this constructor
ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(badgeColor);// change color of border
// use drawble now

答案 4 :(得分:0)

对我来说,它的工作方式如下:(也适用于Android版&gt;棒棒糖)

    ImageView target = (ImageView) findViewById(R.id.imageview);

    GradientDrawable shapeRing = new GradientDrawable();
    shapeRing.setShape(GradientDrawable.OVAL);
    shapeRing.setColor(centerColor); // transparent
    shapeRing.setStroke(stroke, strokeColor);
    shapeRing.setSize(width, width);

    target.setImageDrawable(ring);