Creating ring shape in Android code

浪尽此生 提交于 2019-11-29 08:48:35

Ring and other shapes are GradientDrawables.

If you look at the source code for GradientDrawable, you'll see it looks like certain properties (like innerRadius) can only be defined through XML... they are not exposed through accessor methods. The relevant state is also unhelpfully private to the class, so subclassing is no help either.

Reuben already pointed out most the most useful observations, so I'll just focus on the implementation side of the story. There's multiple approaches using reflection that'll probably give you what you're looking for.

First one is to (ab)use the private GradientDrawable constructor that takes a GradientState reference. Unfortunately the latter is a final subclass with package visibility, so you can't easily get access to it. In order to use it, you would need to dive further in using reflection or mimic its functionality into your own code.

Second approach is to use reflection to get the private member variable mGradientState, which fortunately has a getter in the form of getConstantState(). This'll give you the ConstantState, which at runtime is really a GradientState and hence we can use reflection to access its members and change them at runtime.

In order to support above statements, here's a somewhat basic implementation to create a ring-shaped drawable from code:

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;
    }

}

Above can be used as follows to create a RingDrawable from code and display it in a standard ImageView.

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

This will show a simple, opaque blue ring in the ImageView (10 units inner radius, 20 units thick). You'll need to make sure to not set the ImageView's width and height to wrap_content, unless you add ring.setSize(width, height) to above code in order for it to show up.

Hope this helps you out in any way.

You can do something like this:

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;
}

It is possible to do it from code:

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

For me it works as follow: (also for Android version > lollipop)

    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);

工具导航Map