如何在Drawable XML中自定义Shape类?加载ExtOvalShape时崩溃
问题分析与解决方案
核心问题
- XML结构错误:Selector的每个
<item>只能包含一个Drawable,你在单个<item>里放了两个ExtOvalShape元素,不符合Drawable XML语法规范。 - 自定义Shape使用方式错误:
ExtOvalShape继承的OvalShape属于android.graphics.drawable.shapes.Shape,它不是Drawable子类,不能直接在Drawable XML中作为节点声明,必须通过ShapeDrawable(或自定义Drawable子类)包装使用。
正确实现步骤
步骤1:修正Selector XML结构
每个状态对应独立的<item>,每个<item>内只放一个自定义Drawable:
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:state_selected="true"> <com.xxx.ui.widget.drawable.ExtOvalDrawable app:solid_color="@color/theme_accent_1" app:stroke_width="1dp" app:stroke_color="@color/background_4" app:shape_width="@dimen/part_number_value" app:shape_height="@dimen/part_number_value" /> </item> <item android:state_selected="false"> <com.xxx.ui.widget.drawable.ExtOvalDrawable app:solid_color="@android:color/transparent" app:stroke_width="1dp" app:stroke_color="@color/background_4" app:shape_width="@dimen/part_number_value" app:shape_height="@dimen/part_number_value" /> </item> </selector>
步骤2:定义自定义Drawable包装Shape
创建ExtOvalDrawable继承ShapeDrawable,内部使用你的ExtOvalShape,同时处理XML属性解析:
public class ExtOvalDrawable extends ShapeDrawable { public ExtOvalDrawable() { super(new ExtOvalShape()); } // 必须实现带AttributeSet的构造方法,用于XML加载 public ExtOvalDrawable(AttributeSet attrs) { super(new ExtOvalShape()); Context context = ContextCompat.getDrawableContext(attrs); if (context == null) { return; } // 解析自定义属性 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExtOvalDrawable); // 解析填充色 int solidColor = typedArray.getColor(R.styleable.ExtOvalDrawable_solid_color, Color.TRANSPARENT); getPaint().setColor(solidColor); // 解析描边 int strokeWidth = typedArray.getDimensionPixelSize(R.styleable.ExtOvalDrawable_stroke_width, 0); int strokeColor = typedArray.getColor(R.styleable.ExtOvalDrawable_stroke_color, Color.TRANSPARENT); if (strokeWidth > 0) { getPaint().setStrokeWidth(strokeWidth); // 同时支持填充和描边 Paint paint = getPaint(); paint.setStyle(Paint.Style.FILL_AND_STROKE); // 先画填充再画描边,需要保存原填充色 int originalColor = paint.getColor(); paint.setColor(solidColor); super.draw(canvas); paint.setColor(strokeColor); super.draw(canvas); paint.setColor(originalColor); paint.setStyle(Paint.Style.FILL); } // 解析尺寸 int width = typedArray.getDimensionPixelSize(R.styleable.ExtOvalDrawable_shape_width, 0); int height = typedArray.getDimensionPixelSize(R.styleable.ExtOvalDrawable_shape_height, 0); if (width > 0 && height > 0) { setIntrinsicWidth(width); setIntrinsicHeight(height); } typedArray.recycle(); } @Override public void draw(Canvas canvas) { super.draw(canvas); // 这里可以添加自定义绘制逻辑,比如在椭圆上额外绘制标记 } }
步骤3:定义自定义属性
在res/values/attrs.xml中添加对应的属性,让XML可以配置填充、描边、尺寸:
<resources> <declare-styleable name="ExtOvalDrawable"> <attr name="solid_color" format="color|reference" /> <attr name="stroke_width" format="dimension|reference" /> <attr name="stroke_color" format="color|reference" /> <attr name="shape_width" format="dimension|reference" /> <attr name="shape_height" format="dimension|reference" /> </declare-styleable> </resources>
步骤4:保留自定义Shape类
你的ExtOvalShape可以继续使用,如需修改椭圆绘制逻辑,直接在draw方法扩展:
public class ExtOvalShape extends OvalShape { @Override public void draw(Canvas canvas, Paint paint) { super.draw(canvas, paint); // 这里可以添加自定义逻辑,比如调整椭圆的绘制范围、添加额外图形等 } }
关键说明
Shape是纯绘制逻辑类,不具备Drawable的生命周期和属性解析能力,必须通过ShapeDrawable或自定义Drawable封装后才能在XML中使用。- 自定义Drawable必须实现带
AttributeSet的构造方法,否则XML无法加载该类。 - 同时使用填充和描边时,需要分两次绘制(先填充后描边),确保两者都能正确显示。
内容的提问来源于stack exchange,提问作者anthony
相关产品推荐
相关产品推荐

