在Android开发中,selector是一种通过XML描述不同视图状态下展示不同Drawable资源的机制。它通常被放在res/drawable目录下,以<selector>作为根标签,内部用多个<item>来声明某种状态对应的图片或颜色。当控件如Button、ImageView设置了该selector作为背景或前景时,系统会根据当前控件的状态,如是否按下、是否获得焦点、是否选中,自动从item列表中自上而下寻找第一个匹配的项并渲染。理解item如何引用drawable里的图片资源,是做出正确状态反馈效果的基础。

selector文件的基本结构与item引用语法
一个标准的selector XML文件,其根元素必须是转义后的<selector>标签,而每一个状态分支都用<item>表示。在item中引用drawable目录里的图片资源,核心语法是android:drawable="@drawable/资源名"。这里的资源名指的是放在res/drawable或其限定符目录(如drawable-hdpi)中的文件名,但不包括扩展名。例如图片文件名为btn_pressed.png,引用时就写@drawable/btn_pressed。Android编译系统会在构建期将这些资源映射为整型ID,运行时通过Resource表查找。
需要注意,item标签本身并不限制只能引用图片,它也可以引用颜色(@color/xxx)或者其他drawable类型的XML(如shape)。但在引用图片时,图片必须真实存在于drawable资源体系内,不能放在assets目录或者用文件路径直接填。如果写成了android:drawable="@mipmap/icon"虽然能编译过,但违背了资源分类约定,且在多状态切换时可能增加维护成本。下面展示一个按钮按压切换两张图片的selector示例:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/btn_pressed" />
<item android:state_focused="true" android:drawable="@drawable/btn_focused" />
<item android:drawable="@drawable/btn_normal" />
</selector>
上面的代码中,默认项(无状态限定)放在最后,这是selector的匹配规则决定的:系统从第一个item开始判断,满足状态条件就用它,都不满足才落到默认项。如果默认项写在前面,后面的按压态永远没机会命中。此外,图片资源名应尽量使用小写字母、数字和下划线,避免在某些系统上出现资源链接错误。
常见引用错误与编译运行期排查
很多人在item里引用图片资源时会碰到“Resource not found”的编译错误,这通常有几个原因。第一是资源名写错,比如文件是btn_pressed.png却写成@drawable/btn_press;第二是图片放错了目录,例如放在了res/raw或者自己建的res/image目录,这些都不属于drawable资源集;第三是文件名带了大写或特殊字符,在Windows上不报错但到Linux构建机就失败。编译期报错信息一般会精确到哪一个item哪一行,对照修复即可。
另一类问题是编译通过但运行时不显示预期图片。这种情况往往是状态没触发,比如给TextView设置selector背景,但TextView默认不可点击,state_pressed永远不会成立,于是只显示默认item。还有的是图片本身是点九图(.9.png),但内容区域或拉伸区域画错,导致不同状态下看起来“像没换图”。此时应检查控件是否设置了clickable="true"、focusable="true",并用Layout Inspector观察实际生效的drawable。代码层面也可以在Activity里手动调用setPressed(true)来调试:
Button btn = findViewById(R.id.demo_btn); btn.setBackgroundResource(R.drawable.btn_selector); // 调试用:强制按压态 btn.setPressed(true); btn.invalidate();
还有一点容易忽略:selector里如果混用图片和shape,某些API级别下可能对状态切换有轻微延迟,尤其是大图。建议对按键类小图做密度适配,不要只放一套xxxhdpi图让系统缩放,否则在低配机上可能造成卡顿或OOM。利用drawable-ldpi到xxxhdpi的分目录放置同名图片,是最稳妥的引用方式。
动态创建selector与代码中引用drawable图片
除了静态XML,有时候我们需要在Java或Kotlin代码里动态构造selector并引用drawable里的图片。Android提供了StateListDrawable类,它等价于XML里的<selector>。通过addState方法,我们可以把状态数组和对应的Drawable对象加进去,顺序同样是从具体到默认。引用drawable图片时,用ContextCompat.getDrawable(context, R.drawable.xxx)获取Drawable实例,再传给addState。这种方式适合那些状态分支需要按后台配置动态生成的场景。
下面是一个用代码构建selector并引用图片资源的例子,逻辑与前面的XML完全等价:
StateListDrawable sld = new StateListDrawable();
Drawable pressed = ContextCompat.getDrawable(this, R.drawable.btn_pressed);
Drawable focused = ContextCompat.getDrawable(this, R.drawable.btn_focused);
Drawable normal = ContextCompat.getDrawable(this, R.drawable.btn_normal);
sld.addState(new int[]{android.R.attr.state_pressed}, pressed);
sld.addState(new int[]{android.R.attr.state_focused}, focused);
sld.addState(new int[]{}, normal);
Button btn = findViewById(R.id.demo_btn);
btn.setBackground(sld);
从维护角度看,静态XML更易读且能被编译期检查,而动态方式更灵活。无论哪种,item(或addState)引用drawable图片的本质都是一样的:资源必须进drawable体系、名称匹配、状态顺序正确。在项目里推荐把通用selector抽取到基础库,业务层只关注图片资源产出,这样既能减少重复XML,也降低引用出错概率。