Android 控件美化实战:Shape、Selector 与 layer-list

    |     2026年9月5日   |   Android UI界面, Android技术   |     0 条评论   |    42

适用环境:Android Studio + Java(无 Kotlin、无 AndroidX 依赖),minSdk 14+
阅读收获:3 张速查表 + 26 个可直接复制的示例 + 1 套完整计算器皮肤


一、先搞清楚:为什么要学这三个东西

新建一个 Button,Android 给的默认长相是这样:灰底、直角、按下时一层灰蒙蒙的效果。
产品同学看一眼就会说:”能不能做成圆角的?按下去能不能变色?“

这就是 Shape、Selector、layer-list 存在的理由。它们三个的分工非常清晰:

角色 回答的问题 类比
Shape 这个控件长什么样?(圆角?渐变?描边?) 画笔画出的”一张图”
Selector 不同状态下用哪张图?(按下?选中?禁用?) 一张”状态 → 图”的对照表
layer-list 多张图怎么叠在一起?(阴影、单边线、双层边) 多张透明胶片叠放

口诀记一句就够:

Shape 画形,Selector 管态,layer-list 叠加。


二、Drawable 资源:它们住在哪里

三个东西本质上都是 Drawable 资源,统一放在 res/drawable/ 目录下(早期工程也可能叫 res/drawable-xxhdpi,不推荐)。

app/src/main/res/
  drawable/
  bg_display.xml      ← Shape
  sel_key_num.xml     ← Selector
  bg_card_shadow.xml    ← layer-list
  color/          ← 颜色状态选择器(第五节会讲)
  sel_text_color.xml
  values/
  colors.xml

引用方式(两种,必须会背):

<!-- XML 中:@drawable/文件名(不带 .xml 后缀) -->
<Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/sel_key_num" />
// Java 中:R.drawable.文件名
Button btnNum = (Button) findViewById(R.id.btn_7);
btnNum.setBackgroundResource(R.drawable.sel_key_num);

// 或拿到 Drawable 对象(老 API)
Drawable d = getResources().getDrawable(R.drawable.sel_key_num);

两条工程规范(从第一天就要养成):

  1. 文件名全小写 + 下划线,如 bg_key_num_normal.xml,不要出现大写和中文。
  2. 颜色一律写进 res/values/colors.xml 再用 @color/xxx 引用,不要在 shape 里硬编码 #455A64。改主题时你会感谢自己。

三、Shape 篇:用 XML 画形状

3.1 整体骨架

示例 1:Shape 的标准骨架

res/drawable/demo_shape_skeleton.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <!-- ① 填充色 -->
  <solid android:color="@color/num_normal" />
  <!-- ② 渐变(与 solid 二选一) -->
  <!-- <gradient ... /> -->
  <!-- ③ 描边 -->
  <stroke
    android:width="1dp"
    android:color="@color/fun_normal" />
  <!-- ④ 圆角 -->
  <corners android:radius="6dp" />
  <!-- ⑤ 内边距 -->
  <padding
    android:left="8dp"
    android:top="8dp"
    android:right="8dp"
    android:bottom="8dp" />
  <!-- ⑥ 固有尺寸 -->
  <!-- <size android:width="48dp" android:height="48dp" /> -->
</shape>

关键点:根节点 <shape> 只有一个核心属性 android:shape,子节点共 6 个,全部可选。子节点书写顺序不影响效果,但影响”覆盖关系”(见 3.8 易错点)。


3.2 android:shape:四种形状

示例 2:矩形(rectangle,默认值,最常用)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="#455A64" />
  <corners android:radius="8dp" />
</shape>

示例 3:椭圆(oval)—— 圆形头像、圆点指示器

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="oval">
  <solid android:color="#E53935" />
  <!-- 想画正圆:让控件的 layout_width 与 layout_height 相等即可 -->
  <size android:width="56dp" android:height="56dp" />
</shape>

提示:oval 画的是”内切椭圆”。控件是正方形就得到正圆,是长方形就得到椭圆——所以圆形头像要保证宽高相等,用 size 只是给它一个固有尺寸,最终还是以控件实际大小为准。

示例 4:直线(line)—— 分隔线

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="line">
  <stroke
    android:width="1dp"
    android:color="#BDBDBD"
    android:dashWidth="6dp"
    android:dashGap="4dp" />
  <!-- line 必须配 stroke,否则什么都看不见 -->
  <size android:height="1dp" />
</shape>

示例 5:环形(ring)—— 加载圈、进度环

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="ring"
  android:innerRadius="20dp"   <!-- 内圆半径 -->
  android:thickness="6dp"    <!-- 环的厚度 -->
  android:useLevel="false">    <!-- 关键:false 才显示完整圆环 -->
  <solid android:color="#1E88E5" />
  <size android:width="56dp" android:height="56dp" />
</shape>

⚠️ 高频坑:ring 默认 useLevel="true",此模式下它会把自己当成 LevelListDrawable 使用,level 为 0 时什么都不显示。只要不是拿它做进度条,必须显式写 android:useLevel="false"。


3.3 <solid>:纯色填充

示例 6:纯色圆角块

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/num_normal" />
  <corners android:radius="6dp" />
</shape>

只有一个属性 android:color,可以写 #RGB / #ARGB / #RRGGBB / #AARRGGBB,也可以写 @color/xxx。


3.4 <gradient>:渐变

示例 7:线性渐变(linear,默认)—— 从蓝到青,从左到右

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <gradient
    android:startColor="#1565C0"
    android:centerColor="#1E88E5"
    android:endColor="#4DD0E1"
    android:angle="0"
    android:type="linear" />
  <corners android:radius="8dp" />
</shape>

android:angle 方向对照表(必须理解,不是随意填的):

angle 渐变方向 angle 渐变方向
0(默认) 左 → 右 180 右 → 左
45 左下 → 右上 225 右上 → 左下
90 下 → 上 270 上 → 下
135 右下 → 左上 315 左上 → 右下

记忆方法:angle 从”左→右”开始,按逆时针方向递增。

⚠️ 纠错提示:很多网传资料和旧课件把 angle=90 写成”从上到下”,这是错的。90 是从下到上,想要”从上到下”要写 270。另外 angle 必须是 45 的整数倍,写 30 会被忽略(不报错,直接不生效,最难排查)。

示例 8:径向渐变(radial)—— 从中心向外扩散

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <gradient
    android:startColor="#FFFFFF"
    android:endColor="#000000"
    android:type="radial"
    android:gradientRadius="80dp"
    android:centerX="0.5"
    android:centerY="0.5" />
</shape>

type="radial" 必须配 android:gradientRadius,否则运行时抛异常。centerX / centerY 取值 0.0~1.0,是相对比例,0.5 即正中心。

示例 9:扫描渐变(sweep)—— 雷达扫描、色环

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="oval">
  <gradient
    android:startColor="#FF5252"
    android:endColor="#FFEB3B"
    android:type="sweep"
    android:centerX="0.5"
    android:centerY="0.5" />
</shape>

<gradient> 属性速查:

属性 含义 备注
startColor / centerColor / endColor 起始 / 中间 / 结束颜色 centerColor 可选
angle 渐变角度 45 的整数倍,仅 linear 有效
type linear(默认) / radial / sweep
gradientRadius 径向渐变半径 radial 必填
centerX / centerY 渐变中心点 0.0~1.0,radial / sweep 用
useLevel 是否按 level 取值 常规画形状设 false

3.5 <stroke>:描边与虚线

示例 10:实线描边 —— 描边按钮(Outline Button)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="#00FFFFFF" />   <!-- 全透明填充,只留边 -->
  <stroke
    android:width="2dp"
    android:color="@color/fun_normal" />
  <corners android:radius="20dp" />
</shape>

示例 11:虚线描边 —— 优惠券虚线框

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="#FFF8E1" />
  <stroke
    android:width="1dp"
    android:color="#FFA726"
    android:dashWidth="6dp"   <!-- 每一小段实线的长度 -->
    android:dashGap="4dp" />  <!-- 段与段之间的空隙 -->
  <corners android:radius="4dp" />
</shape>

⚠️ 虚线不显示的两个原因:

  1. android:width 为 0 或没写 —— 没宽度自然没线。
  2. 硬件加速下的老 bug(多见于 4.x 设备某些自定义 View)—— 给该 View 加 android:layerType="software" 即可绕过。

3.6 <corners>:圆角

示例 12:四角统一圆角

<corners android:radius="8dp" />

示例 13:单独指定四个角 —— 消息气泡(左下角是尖的)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="#DCF8C6" />
  <corners
    android:topLeftRadius="10dp"
    android:topRightRadius="10dp"
    android:bottomRightRadius="10dp"
    android:bottomLeftRadius="0dp" />
</shape>

示例 14:胶囊形按钮(radius ≥ 高度的一半)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/fun_normal" />
  <corners android:radius="999dp" />   <!-- 写大一点,自动被裁成半圆 -->
</shape>
<!-- 配套:控件高度固定 40dp,radius 只要 ≥ 20dp 就是胶囊 -->
<TextView
  android:layout_width="wrap_content"
  android:layout_height="40dp"
  android:background="@drawable/bg_capsule"
  android:gravity="center"
  android:paddingLeft="20dp"
  android:paddingRight="20dp"
  android:text="立即领取" />

⚠️ 注意:radius 与四个单独属性同时存在时,单独属性优先级更高。另外 Android 有个渲染小 bug:当 radius 小于 1dp 时部分机型不生效。


3.7 <padding> 与 <size>

示例 15:Shape 自带的 padding(让文字不贴边)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="#263238" />
  <corners android:radius="8dp" />
  <padding
    android:left="16dp"
    android:top="12dp"
    android:right="16dp"
    android:bottom="12dp" />
</shape>

对比:两种 padding 的区别

写法 位置 影响范围 推荐度
控件上的 android:padding 布局 XML 的 View 标签里 只影响内容(文字)位置 ✅ 推荐
Shape 里的 <padding> drawable XML 里 影响内容 + 背景的绘制区域(背景向内缩) 特定场景用

<size> 给 Drawable 定义固有宽高,主要配合 ImageView 的 scaleType 使用;给 View 当 background 时会被控件实际尺寸覆盖,所以不要指望用它”控制控件大小”。


3.8 Shape 属性速查表

子节点 作用 常用属性 是否常用
<solid> 纯色填充 color ★★★★★
<gradient> 渐变填充 startColor centerColor endColor angle type gradientRadius centerX/Y ★★★★
<stroke> 描边 width color dashWidth dashGap ★★★★★
<corners> 圆角 radius topLeftRadius topRightRadius bottomLeftRadius bottomRightRadius ★★★★★
<padding> 内边距 left top right bottom ★★
<size> 固有尺寸 width height ★★

3.9 Shape 易错点清单

现象 原因 解法
圆环不显示 ring 的 useLevel 默认为 true 显式加 android:useLevel="false"
渐变方向反了 把 angle=90 当成”从上到下” 90 是下→上,270 才是上→下
角度写了却没变化 angle 不是 45 的整数倍 改成 0/45/90/…/315
radial 崩溃 缺 gradientRadius 补上 android:gradientRadius
虚线看不见 stroke 的 width 为 0 至少 1dp,必要时加 layerType="software"
同时写 solid 和 gradient 只看到一个 二者互斥,后写的覆盖先写的 二选一,不要同时写

四、Selector 篇:让控件”活”起来

Shape 只能画”死”的图。用户按下按钮时的变色、输入框获得焦点时的高亮,全靠 Selector。

4.1 原理与两条铁律

Selector 就是一张**「状态条件 → drawable」的映射表**,系统从上到下逐条判断。

示例 16:❌ 错误顺序 vs ✅ 正确顺序

<!-- ❌ 错误:无条件的默认项写在最前面,后面的 pressed 永远不会命中 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@drawable/bg_key_num_normal" />
  <item android:state_pressed="true"
      android:drawable="@drawable/bg_key_num_pressed" />
</selector>
<!-- ✅ 正确:具体状态在前,默认项兜底放最后 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true"
      android:drawable="@drawable/bg_key_num_pressed" />
  <item android:drawable="@drawable/bg_key_num_normal" />
</selector>

两条铁律:

铁律一:匹配是自上而下的,命中第一条”所有条件都满足”的 item 就立即返回,后面的不再看。
铁律二:不带任何状态条件的 item 是万能兜底项,必须写在最后面。


4.2 状态属性速查表

属性 触发时机 典型用途
state_pressed 手指按下未松开 ★ 按钮按下变色
state_focused 获得焦点(键盘/轨迹球导航、EditText 光标) 输入框高亮边框
state_selected 被选中(需代码 view.setSelected(true)) Tab、标签切换
state_checked 勾选(CheckBox / RadioButton / ToggleButton) 开关类控件
state_checkable 可被勾选 与 checked 搭配
state_enabled 是否可用(false 即禁用) 提交按钮置灰
state_activated 持久激活态(ListView 单选需要) 列表项选中
state_window_focused 当前窗口是否获得焦点 通知栏下拉/锁屏时
state_hovered 鼠标悬停(API 14+) 电视/平板

提示:state_selected 不会自动触发,必须在 Java 里调用 view.setSelected(true);而 state_pressed、state_focused、state_checked 由系统自动维护。


4.3 写法一:引用外部 drawable(推荐,结构清晰)

示例 17:计算器数字键(示例工程同款)

第一步,建两张 shape:

res/drawable/bg_key_num_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/num_normal" />
  <corners android:radius="6dp" />
</shape>

res/drawable/bg_key_num_pressed.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/num_pressed" />
  <corners android:radius="6dp" />
</shape>

第二步,用 selector 把它们串起来:

res/drawable/sel_key_num.xml

<?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/bg_key_num_pressed" />
  <!-- 默认(必须放最后) -->
  <item android:drawable="@drawable/bg_key_num_normal" />
</selector>

第三步,布局引用:

<Button
  android:id="@+id/btn_7"
  style="@style/CalcKey"
  android:background="@drawable/sel_key_num"
  android:text="7" />

4.4 写法二:内嵌 shape(少建文件,推荐日常使用)

示例 18:一个文件搞定”按下/禁用/默认”三种状态

res/drawable/sel_btn_primary.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- ① 禁用态:置灰 -->
  <item android:state_enabled="false">
    <shape android:shape="rectangle">
      <solid android:color="#BDBDBD" />
      <corners android:radius="20dp" />
    </shape>
  </item>

  <!-- ② 按下态:深色 -->
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#E65100" />
      <corners android:radius="20dp" />
    </shape>
  </item>

  <!-- ③ 默认态:渐变 + 描边 -->
  <item>
    <shape android:shape="rectangle">
      <gradient
        android:startColor="#FFA726"
        android:endColor="#EF6C00"
        android:angle="270" />
      <corners android:radius="20dp" />
    </shape>
  </item>

</selector>

关键:<item> 里可以直接写 <shape> 作为子元素,省掉一堆单独文件。项目里按钮样式多时,这种写法能让 drawable 目录清爽一半。


4.5 颜色选择器:给文字做状态切换

示例 19:按下时文字也跟着变色

背景能切,文字当然也能。区别是:颜色选择器要放在 res/color/ 目录下,用 android:textColor 引用。

res/color/sel_text_tab.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_selected="true" android:color="#EF6C00" />
  <item android:state_pressed="true" android:color="#FFA726" />
  <item android:color="#757575" />
</selector>
<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:textColor="@color/sel_text_tab"
  android:text="首页" />

⚠️ 注意细节:颜色 selector 用的是 android:color 属性,不是 android:drawable。放错目录(res/drawable/)会导致 textColor 引用报错或失效。


4.6 输入框焦点高亮

示例 20:EditText 获得焦点时边框变成主题色

res/drawable/sel_edittext_border.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

  <item android:state_focused="true">
    <shape android:shape="rectangle">
      <solid android:color="#FFFFFF" />
      <stroke android:width="2dp" android:color="#1E88E5" />
      <corners android:radius="4dp" />
      <padding android:left="10dp" android:top="10dp"
           android:right="10dp" android:bottom="10dp" />
    </shape>
  </item>

  <item>
    <shape android:shape="rectangle">
      <solid android:color="#FFFFFF" />
      <stroke android:width="1dp" android:color="#BDBDBD" />
      <corners android:radius="4dp" />
      <padding android:left="10dp" android:top="10dp"
           android:right="10dp" android:bottom="10dp" />
    </shape>
  </item>

</selector>
<EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/sel_edittext_border"
  android:hint="请输入手机号" />

4.7 列表选中态

示例 21:ListView 项被选中时高亮

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 按下(手指还按着) -->
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#BBDEFB" />
    </shape>
  </item>
  <!-- 选中并持久保持(ListView 设了 choiceMode 时) -->
  <item android:state_activated="true">
    <shape android:shape="rectangle">
      <solid android:color="#E3F2FD" />
    </shape>
  </item>
  <!-- 默认 -->
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#FFFFFF" />
    </shape>
  </item>
</selector>

提示:ListView 的持久选中要用 state_activated 配合 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);只写 state_selected 在触摸模式下不会保持。


4.8 禁用态

示例 22:按钮不可用时置灰

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_enabled="false">
    <shape android:shape="rectangle">
      <solid android:color="#E0E0E0" />
      <stroke android:width="1dp" android:color="#BDBDBD" />
      <corners android:radius="6dp" />
    </shape>
  </item>
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#E65100" />
      <corners android:radius="6dp" />
    </shape>
  </item>
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#EF6C00" />
      <corners android:radius="6dp" />
    </shape>
  </item>
</selector>
// Java 中切换可用性
btnSubmit.setEnabled(false);   // 自动命中 state_enabled="false"

4.9 Selector 易错点清单

现象 原因 解法
按下没反应 默认项写在了最前面 默认项移到最后
文字颜色不变 selector 放在 res/drawable/ 移到 res/color/,属性改 android:color
state_selected 死活不生效 系统不会自动置位 Java 调 view.setSelected(true)
ListView 选中一松手就没了 用了 state_selected 换 state_activated + setChoiceMode()
禁用后点击还有反馈 setEnabled(false) 没调用 或 selector 里漏了 state_enabled="false" 项
写了 state_pressed 但 Button 无变化 主题给 Button 加了默认 backgroundTint 覆盖 用 AppCompatButton 时设 app:backgroundTint="@null"(本课示例工程不依赖 AppCompat,无此问题)

五、layer-list 篇:叠加出复杂效果

5.1 基本结构

示例 23:两张图叠加

res/drawable/bg_layer_demo.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 下层:背景 -->
  <item android:drawable="@drawable/faceback" />
  <!-- 上层:头像,向内偏移 -->
  <item
    android:drawable="@drawable/h001"
    android:left="10dp"
    android:top="18dp"
    android:right="25dp"
    android:bottom="35dp" />
</layer-list>

规则:<item> 按书写顺序从下往上叠,写在后面的在最上层;left/top/right/bottom 表示该层相对容器的内缩偏移。


5.2 只要一条底边线

示例 24:只有底部有分隔线的输入框(经典技巧)

res/drawable/bg_bottom_line.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 下层:整块背景色就是"线"的颜色 -->
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#1E88E5" />
    </shape>
  </item>
  <!-- 上层:白色块,底部留 1dp 不遮,露出下面那条线 -->
  <item android:bottom="1dp">
    <shape android:shape="rectangle">
      <solid android:color="#FFFFFF" />
    </shape>
  </item>
</layer-list>

提示:这是”遮罩法“——先铺满线色,再用主色块留出 1dp 的缝。想要顶边线就把 android:bottom 换成 android:top。


5.3 简易阴影

示例 25:用两层偏移矩形模拟投影

res/drawable/bg_card_shadow.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 阴影层:灰色,向下偏移 2dp -->
  <item
    android:left="2dp"
    android:top="2dp"
    android:right="0dp"
    android:bottom="0dp">
    <shape android:shape="rectangle">
      <solid android:color="#33000000" />
      <corners android:radius="8dp" />
    </shape>
  </item>
  <!-- 卡片层:白色,盖在上面 -->
  <item
    android:right="2dp"
    android:bottom="2dp">
    <shape android:shape="rectangle">
      <solid android:color="#FFFFFF" />
      <corners android:radius="8dp" />
    </shape>
  </item>
</layer-list>

Android 5.0+ 有 android:elevation 可以出真实阴影,但 4.x 设备上只能靠这种”假阴影”兜底,minSdk 14 的工程必须会这一招。


5.4 双层描边

示例 26:外白内蓝的双色边框(头像框常见效果)

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 外层:白色描边 4dp -->
  <item>
    <shape android:shape="oval">
      <solid android:color="#FFFFFF" />
      <size android:width="72dp" android:height="72dp" />
    </shape>
  </item>
  <!-- 内层:蓝色描边,向内缩 4dp 露出白边 -->
  <item
    android:left="4dp"
    android:top="4dp"
    android:right="4dp"
    android:bottom="4dp">
    <shape android:shape="oval">
      <solid android:color="#1E88E5" />
      <stroke android:width="2dp" android:color="#0D47A1" />
    </shape>
  </item>
</layer-list>

六、综合实战:给计算器换一套皮肤

把前面所有知识点串起来,完成计算器界面。

Step 1:先定颜色(res/values/colors.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="display_bg">#263238</color>  <!-- 显示屏底 -->
  <color name="display_text">#FFFFFF</color>
  <color name="num_normal">#455A64</color>  <!-- 数字键 默认 -->
  <color name="num_pressed">#607D8B</color>   <!-- 数字键 按下 -->
  <color name="fun_normal">#EF6C00</color>  <!-- 运算键 默认 -->
  <color name="fun_pressed">#FFA726</color>   <!-- 运算键 按下 -->
</resources>

Step 2:画 5 张 Shape

<!-- bg_display.xml:显示屏,深底圆角 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/display_bg" />
  <corners android:radius="8dp" />
</shape>
<!-- bg_key_num_normal.xml / bg_key_num_pressed.xml:数字键 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/num_normal" />
  <corners android:radius="6dp" />
</shape>
<!-- bg_key_fun_normal.xml / bg_key_fun_pressed.xml:运算键 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <solid android:color="@color/fun_normal" />
  <corners android:radius="6dp" />
</shape>

Step 3:用 2 个 Selector 串状态

<!-- sel_key_num.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true" android:drawable="@drawable/bg_key_num_pressed" />
  <item android:drawable="@drawable/bg_key_num_normal" />
</selector>
<!-- sel_key_fun.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true" android:drawable="@drawable/bg_key_fun_pressed" />
  <item android:drawable="@drawable/bg_key_fun_normal" />
</selector>

Step 4:布局引用 + style 复用

res/values/styles.xml

<style name="CalcKey">
  <item name="android:layout_width">0dp</item>
  <item name="android:layout_height">match_parent</item>
  <item name="android:layout_weight">1</item>
  <item name="android:layout_margin">4dp</item>
  <item name="android:textSize">24sp</item>
  <item name="android:textColor">#FFFFFF</item>
</style>

res/layout/activity_calculator.xml(片段)

<TextView
  android:id="@+id/tv_display"
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:background="@drawable/bg_display"
  android:gravity="end|center_vertical"
  android:padding="16dp"
  android:text="0"
  android:textColor="@color/display_text"
  android:textSize="36sp" />
<!-- 按键区省略,每个 Button 用 style="@style/CalcKey"
   + android:background="@drawable/sel_key_num"(或 sel_key_fun) -->

示例工程文件对应关系:

课件知识点 示例工程中的文件
solid + corners res/drawable/bg_display.xml、bg_key_num_normal.xml
状态切换 res/drawable/sel_key_num.xml、sel_key_fun.xml
布局引用 res/layout/activity_calculator.xml
样式复用 res/values/styles.xml 的 CalcKey
颜色集中管理 res/values/colors.xml

七、调试与排查清单

现象 可能原因 排查动作
背景完全没变化 drawable 文件名写错 / 没同步 看 XML 是否报红;Build → Clean Project
预览正常,真机上不对 用了高于 minSdk 的属性 降低 targetSdk 或做版本兼容
圆角被内容”切掉” 内容超出背景范围 检查 View 的 padding 与背景 <padding>
只在部分机型异常 厂商 ROM 对主题做了覆盖 显式设 android:background,不依赖主题默认值
改了 XML 没生效 编译缓存 Build → Clean → Rebuild,卸载重装
渐变方向不对 angle 理解反了 对照 3.4 的角度表

快速验证技巧:Android Studio 的布局编辑器可以直接渲染 drawable 预览(打开 XML 文件,右侧 Preview 面板),改一行看一眼,比跑模拟器快十倍。


八、练习(5 题)

  1. 胶囊按钮:用 Shape 写一个高 44dp、圆角为半圆、橙色底、白色文字的”立即登录”按钮。
  2. 虚线优惠券框:浅黄填充 + 橙色 1dp 虚线边框 + 4dp 圆角。
  3. 三态按钮:一个 XML 文件实现”禁用灰 / 按下深蓝 / 默认渐变蓝”三种状态。
  4. 只有底边线的输入框:用 layer-list 实现,聚焦时线变粗变主题色(提示:layer-list 不能直接放 selector,需要再包一层)。
  5. 圆形头像框:外圈白色 4dp、内圈蓝色描边的双层圆形边框(用 layer-list + oval)。

九、一页纸速查表

Shape

<shape android:shape="rectangle|oval|line|ring">
  <solid   android:color="#RRGGBB" />
  <gradient android:startColor="#" android:centerColor="#" android:endColor="#"
        android:angle="0|45|...|315" android:type="linear|radial|sweep"
        android:gradientRadius="80dp" />
  <stroke  android:width="1dp" android:color="#" 
       android:dashWidth="6dp" android:dashGap="4dp" />
  <corners android:radius="8dp" />
  <padding android:left="" android:top="" android:right="" android:bottom="" />
  <size  android:width="" android:height="" />
</shape>

Selector

<selector>
  <item android:state_pressed="true"  android:drawable="@drawable/xxx" />
  <item android:state_enabled="false" android:drawable="@drawable/xxx" />
  <item android:drawable="@drawable/xxx" />   <!-- 默认项,永远放最后 -->
</selector>

layer-list

<layer-list>
  <item android:drawable="@drawable/下层" />
  <item android:drawable="@drawable/上层"   <!-- 后写的在上 -->
      android:left="" android:top="" android:right="" android:bottom="" />
</layer-list>

附:知识点 ↔ 示例索引

序号 知识点 示例
1 Shape 骨架 示例 1
2 rectangle 示例 2
3 oval 示例 3
4 line 示例 4
5 ring + useLevel 示例 5
6 solid 填充 示例 6
7 gradient 线性渐变 + angle 示例 7
8 gradient 径向渐变 示例 8
9 gradient 扫描渐变 示例 9
10 stroke 实线描边 示例 10
11 stroke 虚线描边 示例 11
12 corners 统一圆角 示例 12
13 corners 分角设置 示例 13
14 corners 胶囊形 示例 14
15 padding / size 示例 15
16 Selector 匹配顺序 示例 16
17 Selector 引用 drawable 示例 17
18 Selector 内嵌 shape 示例 18
19 颜色 selector(res/color) 示例 19
20 state_focused 输入框 示例 20
21 state_activated 列表选中 示例 21
22 state_enabled 禁用态 示例 22
23 layer-list 叠加 示例 23
24 layer-list 单边线 示例 24
25 layer-list 阴影 示例 25
26 layer-list 双层边框 示例 26
27 综合:计算器皮肤 第六节
转载请注明来源:Android 控件美化实战:Shape、Selector 与 layer-list
本文链接地址:https://ai.zhousir.top/?p=3954
回复 取消