适用环境: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
引用方式(两种,必须会背):
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/sel_key_num" />
Button btnNum = (Button) findViewById(R.id.btn_7);
btnNum.setBackgroundResource(R.drawable.sel_key_num);
Drawable d = getResources().getDrawable(R.drawable.sel_key_num);
两条工程规范(从第一天就要养成):
-
文件名全小写 + 下划线,如
bg_key_num_normal.xml,不要出现大写和中文。
-
颜色一律写进
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" />
<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" />
</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" />
<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" />
<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">
<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>
⚠️ 虚线不显示的两个原因:
-
android:width 为 0 或没写 —— 没宽度自然没线。
-
硬件加速下的老 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>
<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 ✅ 正确顺序
<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>
<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>
btnSubmit.setEnabled(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>
<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">
<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">
<item>
<shape android:shape="oval">
<solid android:color="#FFFFFF" />
<size android:width="72dp" android:height="72dp" />
</shape>
</item>
<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
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/display_bg" />
<corners android:radius="8dp" />
</shape>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/num_normal" />
<corners android:radius="6dp" />
</shape>
<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 串状态
<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>
<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" />
示例工程文件对应关系:
| 课件知识点 |
示例工程中的文件 |
| 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 题)
-
胶囊按钮:用 Shape 写一个高 44dp、圆角为半圆、橙色底、白色文字的”立即登录”按钮。
-
虚线优惠券框:浅黄填充 + 橙色 1dp 虚线边框 + 4dp 圆角。
-
三态按钮:一个 XML 文件实现”禁用灰 / 按下深蓝 / 默认渐变蓝”三种状态。
-
只有底边线的输入框:用 layer-list 实现,聚焦时线变粗变主题色(提示:layer-list 不能直接放 selector,需要再包一层)。
-
圆形头像框:外圈白色 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 |
综合:计算器皮肤 |
第六节 |