Fresco Drawee 圆角与圆形图片实战指南:RoundingParams 配置、原理与权衡
【免费下载链接】frescoAn Android library for managing images and the memory they use.项目地址: https://gitcode.com/gh_mirrors/fr/fresco
在 Android 应用中,并非所有图片都是矩形——圆角卡片、圆形头像、标签气泡等场景随处可见。Fresco 的 Drawee 体系为此提供了完整的圆角(Rounded Corners)与圆形(Circle)支持:SimpleDraweeView只需一行 XML 属性或少量 Java 代码即可完成形状裁剪,且全程不复制位图、不产生额外内存开销。本文以 Fresco 仓库中的官方文档 docs/_docs/rounded-corners-and-circles.md 为主体,结合drawee模块的源码实现,为你讲透两种形状、两种实现方式、全部配置项、底层渲染原理与已知陷阱,读完即可在生产项目中直接落地。
一、设计目标:不加内存开销地“变圆”
Drawee 圆角方案的核心承诺是:所有圆角/圆形效果都在绘制层(Drawable 层)完成,而不是通过拷贝位图来实现。传统做法是创建一张圆角裁剪后的临时 Bitmap,这在图片较大时会产生显著的内存峰值,且与 Fresco 高效管理位图内存的宗旨相悖。Drawee 的RoundedDrawable系列(如 RoundedBitmapDrawable.kt、RoundedColorDrawable.java、RoundedCornersDrawable.java)在绘制时对原始 Drawable 做形状约束,因此内存模型与普通矩形图片完全一致。
二、两种形状:圆形与圆角矩形
图片可以被裁剪成两种基本形状:
- 圆形(Circle):将
roundAsCircle设为true。此时以控件宽高中较小者为直径绘制内切圆,roundedCornerRadius会被忽略。 - 圆角矩形(Rounded Rectangle):将
roundedCornerRadius设为某个值(dimension 类型),四个角统一使用该半径。
矩形还支持四个角各自不同的半径,但这一能力只能通过 Java 代码指定,XML 无法表达(XML 只能通过roundTopLeft、roundBottomRight等布尔开关独立控制某个角是否圆角,半径仍是统一的)。
从源码看,RoundingParams.java 内部用float[] mCornersRadii(长度 8)保存半径,每个角包含 X、Y 两个半径值,按左上、右上、右下、左下顺序排列。对应代码 API:
// 统一半径(像素) RoundingParams.fromCornersRadius(7f); // 四个角各自不同半径(像素),顺序:topLeft, topRight, bottomRight, bottomLeft RoundingParams.fromCornersRadii(10f, 5f, 10f, 5f); // 更精细的 8 值版本:[X, Y] 成对,顺序为左上、右上、右下、左下 RoundingParams.fromCornersRadii(new float[] {10f, 10f, 5f, 5f, 10f, 10f, 5f, 5f});源码中的setCornersRadii(float[] radii)(RoundingParams.java)会校验数组必须恰好包含 8 个值,否则抛出异常;setCornersRadius(float radius)则用Arrays.fill一次填充所有角。工厂方法asCircle()则直接返回setRoundAsCircle(true)的实例(见 RoundingParams.java)。
三、两种实现方式:BITMAP_ONLY 与 OVERLAY_COLOR
圆角可以通过两种不同的方法实现,对应 RoundingParams.java 中的RoundingMethod枚举:
3.1 BITMAP_ONLY(默认)
使用BitmapShader(位图着色器)将位图按圆角路径绘制,是默认方法,对应枚举值BITMAP_ONLY。它有两条硬性限制:
- 不支持动画(GIF、WebP 动图等无法圆角);
- 只支持有限的 ScaleType:
centerCrop(默认)、focusCrop、fit_xy。若搭配center等其他缩放类型,不会抛异常,但显示效果可能错误(例如源图小于控件时出现边缘重复),详见第五节。
从实现看,RoundedBitmapDrawable.kt 中正是paint.setShader(BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))——使用CLAMP 平铺模式绘制位图,这直接解释了"图片未铺满控件时边缘像素被重复填充"的经典现象。
3.2 OVERLAY_COLOR
通过在图片上方叠加一层调用者指定的纯色来"遮出"圆角。使用前提是 Drawee 的背景必须是静态的、与叠加色相同的纯色。XML 中用roundWithOverlayColor指定叠加色,代码中用setOverlayColor指定。
源码细节:setOverlayColor(@ColorInt int overlayColor)在设置颜色值的同时会自动把mRoundingMethod切换为OVERLAY_COLOR(见 RoundingParams.java),无需手动再调setRoundingMethod。
3.3 两种方式的底层分发逻辑
圆角最终由 WrappingUtils.java 负责应用,它根据RoundingMethod走两条完全不同的路径:
- BITMAP_ONLY → 叶子圆角:
updateLeafRounding(WrappingUtils.java)沿 Drawable 链找到最内层的叶子,调用applyLeafRounding将其替换为RoundedBitmapDrawable/RoundedColorDrawable等圆角包装。applyLeafRounding明确只处理BitmapDrawable与ColorDrawable(见 WrappingUtils.java),遇到无法识别的类型会通过FLog.w打印 "Don't know how to round that drawable" 并原样返回。 - OVERLAY_COLOR → 顶层叠加:
updateOverlayColorRounding(WrappingUtils.java)在层级最外层包一层RoundedCornersDrawable,把叠加色画在图片之上。
每次调用GenericDraweeHierarchy.setRoundingParams()时,两个方法会被同步执行,保证两种模式可以随时切换、增删(见 GenericDraweeHierarchy.java)。
四、在 XML 中配置
SimpleDraweeView会将其上声明的圆角相关属性转发给内部的RoundingParams。属性定义在 drawee/src/main/res/values/attrs.xml 的GenericDraweeHierarchystyleable 中,SimpleDraweeView通过继承完整复用(见 attrs.xml)。完整示例:
<com.facebook.drawee.view.SimpleDraweeView android:layout_width="100dp" android:layout_height="100dp" app:actualImageUri="https://example.com/image.jpg" fresco:roundedCornerRadius="5dp" fresco:roundBottomStart="false" fresco:roundBottomEnd="false" fresco:roundWithOverlayColor="@color/blue" fresco:roundingBorderWidth="1dp" fresco:roundingBorderColor="@color/red" />注意 XML 命名空间前缀需与你工程声明一致(示例中为fresco)。全部圆角相关属性及含义如下:
| 属性 | 类型 | 说明 |
|---|---|---|
roundAsCircle | boolean | 是否裁剪为圆形;开启后roundedCornerRadius被忽略 |
roundedCornerRadius | dimension | 统一圆角半径;被roundAsCircle忽略 |
roundTopLeft/roundTopRight/roundBottomLeft/roundBottomRight | boolean | 是否圆角对应角落,默认 true |
roundTopStart/roundTopEnd/roundBottomStart/roundBottomEnd | boolean | 逻辑边(Start/End)版本,自动适配 RTL 布局 |
roundWithOverlayColor | color | 设置后即切换到OVERLAY_COLOR方式 |
roundingBorderWidth | dimension | 圆角边框宽度,默认 0 |
roundingBorderColor | color/reference | 圆角边框颜色,默认透明 |
roundingBorderPadding | dimension | 圆角内边距(仅BITMAP_ONLY生效),默认 0 |
这些属性的解析逻辑在 GenericDraweeHierarchyInflater.java:roundedCornerRadius用getDimensionPixelSize读取(注意:px 单位,XML 中写 dp 会被框架自动换算);roundWithOverlayColor、roundingBorderColor用getColor;布尔开关逐个读取。四个角的最终半径由开关组合决定(见 GenericDraweeHierarchyInflater.java)。
特别值得一提的是 RTL 支持:解析器会检查当前资源配置的布局方向,在 RTL 环境下将roundTopStart解释为右上角、roundBottomEnd解释为左下角等,实现逻辑角与物理角的自动映射(见 GenericDraweeHierarchyInflater.java)。
五、在代码中配置
5.1 构建层级时指定
在构造GenericDraweeHierarchy时,将RoundingParams实例传给GenericDraweeHierarchyBuilder:
RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f); mSimpleDraweeView.setHierarchy(new GenericDraweeHierarchyBuilder(getResources()) .setRoundingParams(roundingParams) .build());setRoundingParams定义于 GenericDraweeHierarchyBuilder.java,构建完成后该实例会被GenericDraweeHierarchy持有并在层级构建时应用。
5.2 层级构建后动态修改
层级构建完成后,所有圆角参数仍然可以随时修改:
int color = getResources().getColor(R.color.red); RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f); roundingParams.setBorder(color, 1.0f); roundingParams.setRoundAsCircle(true); mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);RoundingParams是可变对象、支持链式调用,除上述方法外还提供以下能力(定义于 RoundingParams.java):
setRoundingMethod(RoundingMethod):显式指定BITMAP_ONLY或OVERLAY_COLOR;setBorder(@ColorInt color, float width)/setBorderWidth/setBorderColor:设置圆角边框(宽度为 0 表示无边框,边框颜色默认Color.TRANSPARENT);setPadding(float):圆角内边距,仅BITMAP_ONLY生效,宽度和边距均不可为负(带参数校验);setScaleDownInsideBorders(boolean):图片是否缩放到边框内部(否则边框会画在图片之上);setPaintFilterBitmap(boolean):为 Paint 设置FILTER_BITMAP_FLAG,绘制位图时建议开启(软件渲染或追求像素风时可关闭);setRepeatEdgePixels(boolean):源图小于控件时是否重复边缘像素以填满整个圆角视口,对纯色小图标填充大圆角场景很有用。
完整的类说明可参考仓库内 Javadoc:RoundingParams.html。
六、注意事项与权衡(Caveats)
使用默认的BITMAP_ONLY模式时,存在以下已知限制:
- 只有能解析为
BitmapDrawable或ColorDrawable的图片才能圆角。NinePatchDrawable、ShapeDrawable等其他 Drawable 不支持圆角(无论 XML 还是代码指定)。需要说明的是,从当前仓库源码看,WrappingUtils.applyLeafRounding 已额外支持NinePatchDrawable(包装为RoundedNinePatchDrawable),说明这一限制在演进中有所放宽,但使用前仍建议在目标机型验证。 - 动画无法圆角(GIF、WebP 动图在
BITMAP_ONLY下不生效)。 - Android
BitmapShader的固有缺陷:图片未完全覆盖控件时,空白区域不会绘制为空,而是重复边缘像素(CLAMP 模式,参见 RoundedBitmapDrawable.kt)。两种规避方案:- 换用能铺满整个控件的缩放类型(如
centerCrop); - 在图片文件四周留1px 的透明边框,让透明像素被重复填充——这是 PNG 资源图的最佳解法。
- 换用能铺满整个控件的缩放类型(如
若上述限制影响了你的图片,可以考虑OVERLAY_COLOR模式:它没有上述问题,但本质是用纯色"盖"出圆角,只有当视图下方背景是静态且同色时效果才理想。此外,源码注释还提示该模式下叠加边框会裁剪图像边缘(不透明色时不易察觉,低透明度时非常明显)。
另外两个被否决/禁用的方案:
- CLIPPING(路径裁剪)模式:Drawee 内部保留了
CLIPPING实现(见 RoundedCornersDrawable.java 的枚举与case CLIPPING分支),但该模式已被禁用且未对外暴露——部分Canvas实现不支持路径裁剪,且裁剪不支持抗锯齿,圆角边缘会严重像素化。 - 临时位图方案:用 Bitmap 预裁剪确实能绕开以上所有问题,但会带来显著的内存开销,因此 Fresco 明确不支持。
结论正如官方文档所言:Android 上并不存在真正完美的圆角方案,开发者必须在上述方案与各自代价之间做取舍。
七、Showcase 完整示例
Fresco 的 showcase 示例应用提供了完整的圆角演示。原文档指向的DraweeRoundedCornersFragment在当前仓库中已演进为 Vito 架构下的 VitoRoundedCornersFragment.java,它一次性演示了四种形态:
// 圆形 mImageRoundOptions = mImageRoundOptions.extend().round(RoundingOptions.asCircle()).build(); // 四角统一 16dp 圆角 mImageRadiusOptions = mImageRadiusOptions.extend().round(RoundingOptions.forCornerRadiusPx(radius)).build(); // 只有左上、右下圆角(其余为直角) mImageSomeOptions = mImageSomeOptions.extend().round(RoundingOptions.forCornerRadii(radius, 0, radius, 0)).build(); // 四角各自不同的"花式"圆角 + 动态边框 mImageFancyOptions = mImageFancyOptions.extend().round(RoundingOptions.forCornerRadii(/* 4 个分别来自 dimens.xml 的半径 */)).build();该示例还支持通过 Spinner 实时切换 ScaleType、通过复选框开关圆角边框(BorderOptions.create)以及"图片是否缩放进边框内"(scaleInside),是理解圆角与缩放类型交互的最佳活教材——尤其能直观看到在center等非兼容缩放类型下BITMAP_ONLY的边缘重复问题。
结语
Fresco Drawee 的圆角体系是一套"零拷贝、可配置、可热更新"的绘制层方案:用BITMAP_ONLY(默认)享受位图着色器的高效裁剪,用OVERLAY_COLOR绕开位图限制、换取纯色背景下的简易圆角,必要时用roundingBorder*系列参数叠加边框与内边距。理解RoundingParams的全部 API 与WrappingUtils的分发机制后,无论是 XML 静态布局还是代码动态切换,都能在控制内存开销的前提下稳定输出美观的圆角与圆形图片。
【免费下载链接】frescoAn Android library for managing images and the memory they use.项目地址: https://gitcode.com/gh_mirrors/fr/fresco
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考