【问题标题】:Creating ring shape in Android code在 Android 代码中创建环形
【发布时间】:2020-03-05 16:35:22
【问题描述】:

我有以下形状的 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 以便最终出现在屏幕上的指南,我也会很感激一个链接。谢谢。

【问题讨论】:

  • 一个很好的xml和Java代码之间关系的起点,是LayoutInflater的来源->grepcode.com/file/repository.grepcode.com/java/ext/…
  • 您能解释一下为什么需要这样做吗?你想得到什么结果?
  • @Jin35 我想画一个带有动态变化扫描梯度的环。

标签: android android-layout android-ui


【解决方案1】:

Reuben 已经指出了最有用的观察结果,所以我将只关注故事的实施方面。有多种使用反射的方法可能会给你你正在寻找的东西。

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

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

为了支持上述陈述,这里有一个从代码创建环形可绘制对象的基本实现:

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 可以按如下方式从代码中创建一个 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) 以使其显示。

希望这对您有所帮助。

【讨论】:

  • 在棒棒糖之后它在棒棒糖设备上工作正常,它不工作根本没有效果。我该如何解决这个问题?
【解决方案2】:

戒指和其他形状是GradientDrawables

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

【讨论】:

  • 我想是这样,但感觉麻烦多于其价值。我建议先查看 Canvas.drawCircle 。也许股票drawables只是不适合所需的东西。
  • 我有点困惑。那和android.graphics.LinearGradient这样的类有什么关系?作为另一个(稍微)无关的不,我发现如果 Google 会选择不允许开发人员在代码中做您可以在 XML 中做的事情,这很奇怪。
  • LinearGradient 是一种着色器,可以插入到 Paint 中,因此所有 Canvas.draw*() API 都会以您想要的任何形状绘制漂亮的渐变填充。资源 Drawable 类型的存在是为了提供一个合理的 XML 绘图工具包,不需要您动手使用 Java。
  • 可以在 Java 中做任何你可以在 XML 中做的事情......你不能总是使用 Drawable 类型来做到这一点。 Java/Android 中的实际绘图是使用 Canvas 完成的。
  • 啊,好吧,这开始有意义了。所以换句话说,如果我想在代码中完成类似的事情,我需要使用android.graphics.SweepGradient 并将其绘制在Canvas 上自己创建环?有点遗憾的是,该功能已经很好地封装在高级 API 中,但只能通过 XML 公开。
【解决方案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;
}

【讨论】:

  • 谢谢凯文,这太简单了。
  • 随时@Gotama。 :)
【解决方案4】:

可以通过代码实现:

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

【讨论】:

    【解决方案5】:

    对我来说,它的工作原理如下:(也适用于 Android 版本>棒棒糖)

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-23
      • 1970-01-01
      • 2018-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多