最近项目中有实现阴影的需求,需求是控件左侧或者右侧贴边,未贴边一侧需要圆角,控件背景为白色,整体带0.7的透明,需要阴影。尝试了ShadowLayer还有elevation+outlineProvider两种方案,记录一下。
1. ShadowLayer
该方案是通过自定View,对画笔设置ShadowLayer来绘制图形,代码如下:
class ShadowView : View {
private var viewWidth: Int = 0
private var viewHeight: Int = 0
private var shadowRound = 0f
private var shadowColor = 0
private var shadowRadius = 0f
private var shadowOffsetX = 0f
private var shadowOffsetY = 0f
private lateinit var shadowPaint: Paint
private lateinit var shadowPath: Path
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initAttr(attrs, defStyleAttr)
initPaint()
}
private fun initAttr(attrs: AttributeSet?, defStyleAttr: Int) {
val typeArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ShadowView, defStyleAttr, 0)
shadowRound = typeArray.getDimension(R.styleable.ShadowView_shadowRoundRadius, dp2Px(27).toFloat())
shadowColor = typeArray.getColor(R.styleable.ShadowView_shadowColor, ContextCompat.getColor(context, R.color.alpha_black))
shadowRadius = typeArray.getFloat(R.styleable.ShadowView_shadowRadius, 10f)
shadowOffsetX = typeArray.getDimension(R.styleable.ShadowView_shadowOffsetX, 6f)
shadowOffsetY = typeArray.getDimension(R.styleable.ShadowView_shadowOffsetY, 2f)
}
private fun initPaint() {
shadowPaint = Paint()
shadowPaint.color = ContextCompat.getColor(context, android.R.color.white)
shadowPaint.isAntiAlias = true
shadowPaint.style = Paint.Style.FILL
shadowPaint.alpha = (255 * 0.7).toInt()
}
private fun initPath() {
shadowPath = Path()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
viewWidth = MeasureSpec.getSize(widthMeasureSpec)
viewHeight = MeasureSpec.getSize(heightMeasureSpec)
initPath()
setMeasuredDimension(viewWidth, viewHeight)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//shadowRadius:模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,没有阴影
shadowPaint.setShadowLayer(shadowRadius, shadowOffsetX, shadowOffsetY, shadowColor)
canvas?.drawPath(shadowPath, shadowPaint)
canvas?.save()
}
private fun dp2Px(dpValue: Int): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue.toFloat(), Resources.getSystem().displayMetrics).toInt()
}
}
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<ShadowView
android:id="@+id/v_shadow_test_shadow_layer"
android:layout_width="82dp"
android:layout_height="54dp"
android:layout_marginTop="120dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
实现效果如下图:
2. elevation+outlineProvider
android:elevation 是Android5.0才加入的属性,不过根据官方的统计5.0已经覆盖了98%的设备,所以大可放心使用。代码如下:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<View
android:id="@+id/v_shadow_test_outline"
android:layout_width="82dp"
android:layout_height="54dp"
android:layout_marginTop="120dp"
android:alpha="0.7"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class ShadowActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vShadowOutline.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val width = view.measuredWidth.toFloat()
val height = view.measuredHeight.toFloat()
val radius = height / 2
val path = Path()
path.moveTo(width, 0f)
path.lineTo(0 + radius, 0f)
path.addArc(0f, 0f, 0 + radius * 2, height, 270f, -180f)
path.lineTo(width, height)
path.lineTo(width, 0f)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
outline.setPath(path)
} else {
outline.setConvexPath(path)
}
//阴影透明度,采用这种方式只能调整阴影的透明度,无法调整阴影的颜色
outline.alpha = 0.4f
}
}
}
}
实现效果如下图:
总结
两个方案对于我目前的需求来说都是可以满足的,方案2会比较简单一些,因此目前采用的是方案2。但是方案2也有局限性,就是无法改变阴影的颜色,以后如果有需要彩色的阴影的话就得使用方案1来实现。