stroke必须作为shape的直接子节点,且android:width和android:color为唯二必需属性;描边向内绘制,需配相应padding;虚线需同时设dashWidth和dashGap;ripple可能覆盖描边颜色,建议用layer-list分离处理。

stroke 标签必须写在 shape 内部,且不能嵌套其他标签
Android 的 shape 是个容器,stroke 是它的一个子元素,不是独立可渲染的组件。很多人把 stroke 单独拿出来用,或者套在 solid 外面,结果边框根本不显示——因为 XML 解析直接忽略非法结构。
正确写法只有一种顺序:shape → 可选 corners → 必须有 stroke(如果要描边)→ 可选 solid 或 gradient。顺序错、层级错,stroke 就失效。
-
stroke必须作为shape的直接子节点 - 不能把
stroke放在solid里面,也不能反过来 - 如果同时需要圆角和描边,
corners和stroke是同级兄弟节点
android:width 和 android:color 是唯二必需属性
缺任意一个,stroke 就不会生效——不是报错,而是静默忽略。尤其 android:color 容易被漏掉,比如只设了宽度却忘了颜色,UI 上看起来“没边框”,实际是描边色为透明或默认黑但被背景盖住了。
常见错误现象:TextView 设置了背景 drawable,但边框看不见;检查后发现 XML 里只有 android:width="2dp",没写 android:color。
-
android:width值必须带单位(dp、px),纯数字会解析失败 -
android:color支持十六进制(如#FF4081)、引用色值(如@color/accent),不支持英文色名 - 如果想实现虚线边框,必须额外加
android:dashWidth和android:dashGap,否则仍是实线
描边不居中、视觉变粗?那是 padding 没配平
Android 的 stroke 是向内绘制的:边框占用的是 shape 自身的内部空间。比如一个 100×100dp 的矩形,加了 4dp 描边,实际内容区域就缩成了 92×92dp。如果你没给 View 配 padding,文字或子 View 就会紧贴描边,显得“压线”或“偏窄”。
这不是 bug,是设计行为。很多开发者调了半天 stroke 属性,最后发现是 TextView 缺少 android:padding="4dp" 导致文字撞边。
- 描边宽度 = 实际占用的内边距空间,建议 View 的
padding至少等于stroke的android:width - 如果用在
Button上,还要注意系统默认minHeight可能挤压描边表现 - 在 ConstraintLayout 中,不要依赖描边来“撑开”尺寸——它不参与测量,只负责绘制
API 21+ 的 ripple 效果可能盖掉 stroke 颜色
给带描边的 background 加 ripple 时,点击态的波纹默认是半透明黑色,会叠加在描边上,导致边框颜色变深甚至发灰。这不是描边失效,是图层混合的结果。
解决方法不是去掉 ripple,而是显式控制 ripple 的颜色和边界。最稳妥的方式是用 ripple 包一层 shape,并把 stroke 放在 ripple 的 mask 层之外(即作为 foreground)。
- 避免直接把
ripple当作根节点然后在里面塞stroke—— ripple 的android:color会干扰描边显示 - 更可靠的做法:用
layer-list组合ripple(作为 background) + 独立shape(含stroke,作为 foreground) - 低版本兼容时,
ripple不生效,但stroke仍正常,这点倒不用额外降级处理
dashWidth 但没配 dashGap,或者 width 设成了 0.5dp 这种非整数——部分低端设备对 sub-pixel 渲染支持差,直接跳过绘制。










