Android圆角与自定义绘制:从clipPath到60fps的实战路径
【免费下载链接】awesome-android-uiA curated list of awesome Android UI/UX libraries项目地址: https://gitcode.com/gh_mirrors/aw/awesome-android-ui
头像在Figma里是正圆,上线变成带白边的椭圆,PM在群里@你:“晚上能修好吗?”这个Android圆角的坑,你大概率会碰到。awesome-android-ui 收了一堆现成的圆角组件,但选之前,先搞懂圆角到底怎么画出来的,不然库的API看不懂,只能怪自己。
原理速览:圆角到底是怎么画出来的
所有圆角的本质都是“裁剪”,区别只在裁剪发生在哪一层:
- clipPath:Canvas层裁剪。自己构造 RoundRect 路径,
canvas.clipPath(roundPath)之后画的东西只会出现在路径内。完全可控,代价是每帧都要自己维护路径。 - BitmapShader:Paint层“上釉”。把位图包成 shader 再画形状,系统按像素采样图片,圆角被预烘进位图,滚动时只是贴图。
- ViewOutlineProvider:View层打码。只告诉框架“轮廓是这个形状”,裁剪、阴影它自己处理,你连 onDraw 都不用进,API 21+ 顺带送阴影。
上面这张是仓库里的 Rotatable,旋转缩放全走 Canvas 变换——和圆角同一套“绘制层”思路:你想露出什么,先说清楚裁掉什么。
选型决策表:先定场景再看库
圆角选型不用背,先看这张表:
| 需求场景 | 推荐方案 | 一句话理由 | 性能代价 |
|---|---|---|---|
| 静态背景 | ShapeDrawable(corners 属性) | 一行XML搞定 | 零 |
| 列表里的圆角图 | BitmapShader 预烘(RoundedImageView) | 烘进位图,不逐帧计算 | 一次性位图解码 |
| 跨版本圆角卡片 | CardView / Outline | 框架统一管角和阴影 | 轻微图层开销 |
| 点击半径变化动画 | 自定义View + 逐帧Path | 唯一能逐帧改的办法 | 逐帧CPU建路径 |
| 异形裁剪 | clipPath + Path | 全可控,星形六边形都行 | 逐帧建路径 |
场景拆解
场景A:列表圆角图片不掉帧的写法
问题:一百条的 feed,每格一张 96dp 图,逐帧 clipPath,手指一划,40fps。
解法:别逐帧算,预烘。RoundedImageView 把 BitmapShader 那套封装好了,直接抄作业:
<com.makeramen.roundedimageview.RoundedImageView android:layout_width="96dp" android:layout_height="96dp" app:riv_corner_radius="12dp" app:riv_border_width="1dp" app:riv_border_color="@color/white" android:src="@drawable/avatar"/>列表的写法关键其实在图片装载链路。像 Kiel 这种声明式适配器(下图)一行数据一行图,圆角只是链路最后那 12dp。
为什么这么选:否决了逐帧 clipPath(每帧新路径,CPU 不给力)和逐格 ShapeDrawable(Drawable 不缓存位图,布局一变就算一遍)。烘成位图之后,滚动就是贴图,60fps 稳。
场景B:带阴影圆角卡片的跨版本一致性
问题:同一张卡片,5.0 上圆角干净阴影柔和,4.4 上角被切方、阴影发虚。用户不管你的 minSdk,只管设计裂没裂。
解法:角和阴影一起交给框架,别自己拼:
<android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" app:cardCornerRadius="12dp" app:cardElevation="4dp" app:cardPreventCornerOverlap="true"> <!-- 卡片内容 --> </android.support.v7.widget.CardView>API 21+ 它内部走 Outline,阴影由框架画;老版本 CardView 会自己退化成位图阴影。同一份布局,角是同一个角。
为什么这么选:否决了 9-patch 阴影手搓(每种密度画一张,迟早漏一张)和纯靠系统 elevation(5.0 以下没阴影,裸奔)。
场景C:点击时圆角半径变化的交互动画
问题:关注按钮要从 12dp 胶囊形,点击时长成全圆进度环。两个静态 Drawable 来回切是切不出过渡的——半径本身得是个会动的值。
解法:把圆角半径变成可动画值,逐帧重建 Path:
ValueAnimator anim = ValueAnimator.ofFloat(12f, getHeight() / 2f); anim.addUpdateListener(v -> { cornerRadius = (Float) v.getAnimatedValue(); invalidate(); // onDraw 里用新半径重建 RoundRect }); anim.setDuration(180); anim.start();单按钮、180ms、几帧 CPU 建路径,无伤大雅。
为什么这么选:否决 Lottie——它是预制的矢量动画(下图),顺滑,但半径没法跟业务状态(成功/失败)动态走;也否决 9-patch 切换,瞬间跳变没有过渡感。
性能暗礁
⚠️ 三块礁石,踩得最多。
逐帧建Path→ onDraw 里每帧path.addRoundRect(...)→ 列表滚动每帧16ms,CPU先扛,帧率先掉。
硬件加速+Xfermode→ Manifest 里android:hardwareAccelerated="false"→ 硬件加速一关,文字渲染全走CPU,老机型直接卡。要用 Xfermode 就得认这笔账。
嵌套布局做阴影→ CardView 外面再套一层 FrameLayout 画阴影 → 多一层就多一层过度绘制,列表滚动时 GPU 翻倍。
圆角组件生态速查
分三组,对号入座:
图片圆角
- CircleImageView — 圆形头像最小实现,开销几个KB
- RoundedImageView — 四角半径可独立设,列表格子主力
- SelectableRoundedImageView — 带选中态的版本,适合可勾选列表
容器圆角
- CardView — 官方圆角+阴影容器,跨版本最稳
- Maskable Layout — 任意Path裁剪子控件,clipPath 的容器版
- MaterialShadows — 跨版本一致的阴影,配圆角卡片用
异形裁剪
- ShapeImageView — 图片裁成任意形状,圆形星形一把梭
- android-anyshape — 六边形等多边形,几何老师版异形
- RoundedLetterView — 圆角字母头像,没图时的兜底
完整清单在仓库 README.md,选库前先翻近6个月有没有 commit;三年没动的库,圆角虽然不会坏,依赖迟早坏,别赌。仓库可直接克隆:git clone https://gitcode.com/gh_mirrors/aw/awesome-android-ui
一句话带走
把你项目里第一个 ImageView 换成 RoundedImageView,滑一下列表,感受差距。
【免费下载链接】awesome-android-uiA curated list of awesome Android UI/UX libraries项目地址: https://gitcode.com/gh_mirrors/aw/awesome-android-ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考