Android如何实现Figma/XD中带自定义参数的投影效果?
Android实现Figma/XD自定义投影效果方案
问题核心痛点
原生elevation属性仅支持系统默认的固定黑色半透明阴影,无法实现Figma中标注的自定义偏移、模糊半径、带透明度彩色阴影效果;使用selector、layer-list实现的阴影本质是静态渐变叠加,无真实高斯模糊效果,且会被TextInputLayout的内部布局层级覆盖,仅能在EditText上生效。
可用实现方案
方案1:使用官方Material组件库自带能力(推荐,性能最优)
Material Components 1.2.0及以上版本自带的MaterialShapeDrawable原生支持自定义阴影全参数,无需关闭硬件加速,和TextInputLayout同属官方组件库,适配无冲突。
核心实现步骤:
- 确保项目已引入Material Components依赖
- 直接获取TextInputLayout的内置背景(本身就是MaterialShapeDrawable实例),修改阴影参数即可,无需额外自定义控件或Drawable
示例代码:
// 扩展方法:dp转px fun Int.dpToPx(): Float = this * Resources.getSystem().displayMetrics.density textInputLayout.apply { // 关闭默认描边样式,避免和自定义效果冲突 boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_FILLED val shapeBg = background as MaterialShapeDrawable // 配置圆角参数匹配设计稿 shapeBg.shapeAppearanceModel = shapeBg.shapeAppearanceModel.toBuilder() .setAllCornerSizes(8.dpToPx()) .build() // 设置控件主体填充色 shapeBg.fillColor = ColorStateList.valueOf(Color.WHITE) // 配置阴影参数,完全对齐Figma标注 shapeBg.setShadowColor(Color.parseColor("#1A000000")) // 带透明度的阴影色 shapeBg.shadowRadius = 12.dpToPx() // 模糊半径 shapeBg.shadowOffsetX = 2.dpToPx() // X轴偏移 shapeBg.shadowOffsetY = 4.dpToPx() // Y轴偏移 // 置零原生elevation,避免系统默认阴影叠加 elevation = 0f }
方案2:自定义Drawable实现(无第三方依赖,兼容所有API版本)
如果项目未引入Material组件库,可以通过自定义Drawable基于Paint.setShadowLayer()实现阴影效果,全版本兼容。
核心注意点:需要给控件开启软件渲染层,否则阴影在硬件加速模式下可能不显示;不要将阴影设置给TextInputLayout内部的EditText,要直接设置给TextInputLayout本身,同时关闭TextInputLayout默认的box背景。
核心实现代码:
class CustomShadowDrawable( private val bgColor: Int, private val shadowColor: Int, private val shadowRadius: Float, private val offsetX: Float, private val offsetY: Float, private val cornerRadius: Float ) : Drawable() { private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = bgColor setShadowLayer(shadowRadius, offsetX, offsetY, shadowColor) } override fun draw(canvas: Canvas) { val drawRect = RectF( bounds.left + shadowRadius - offsetX, bounds.top + shadowRadius - offsetY, bounds.right - shadowRadius - offsetX, bounds.bottom - shadowRadius - offsetY ) canvas.drawRoundRect(drawRect, cornerRadius, cornerRadius, paint) } override fun setAlpha(alpha: Int) { paint.alpha = alpha } override fun setColorFilter(colorFilter: ColorFilter?) { paint.colorFilter = colorFilter } override fun getOpacity(): Int = PixelFormat.TRANSLUCENT }
使用方式:
val shadowBg = CustomShadowDrawable( bgColor = Color.WHITE, shadowColor = Color.parseColor("#1A000000"), shadowRadius = 12.dpToPx(), offsetX = 2.dpToPx(), offsetY = 4.dpToPx(), cornerRadius = 8.dpToPx() ) textInputLayout.apply { setLayerType(View.LAYER_TYPE_SOFTWARE, null) background = shadowBg boxBackgroundMode = TextInputLayout.BOX_BACKGROUND_NONE }
适配注意事项
- 两种方案都需要给控件四周留出和阴影半径相等的外边距/内边距,避免阴影被父布局边界裁剪
- 不要同时使用原生
elevation和自定义阴影,否则会出现双层阴影叠加的问题 - 如果需要支持阴影点击事件穿透,自定义Drawable时重写
getPadding方法将阴影区域算入控件padding即可
内容的提问来源于stack exchange,提问作者Taimoor Khan
相关产品推荐
相关产品推荐

