前言
公司要出新的版本,界面的风格有了一些改变,对于一些View要加一些阴影的风格,使用cardview以及elevation实现了阴影,但是达不到设计图的那种阴影效果,所以只有通过自定义了。
感谢lijiankun24,借鉴了对View自定义阴影的封装,对View设置阴影有了些认识
github地址:https://github.com/lijiankun24/ShadowLayout
实现
1.对View实现阴影效果是通过 mPaint.setShadowLayer(float radius, float dx, float dy, int shadowColor)
radius : 模糊半径,radius越大越模糊,越小越清晰,当radius=0时,则阴影消失不见
dx: 阴影在x轴的偏移距离,正值向右偏移,负值向左偏移
dy: 阴影在y轴的偏移距离,正值向下偏移,负值向上偏移
color:阴影的颜色
-
代码
/** * 初始化画笔 */ private fun initPaint() { //画笔重置 mPaint.reset() //设置抗锯齿 mPaint.isAntiAlias = true //设置透明度 mPaint.color = Color.TRANSPARENT /** * 参数1 模糊半径 越大越模糊 * 参数2 阴影在x轴的距离 * 参数3 阴影在y轴的距离 * 参数4 阴影颜色 */ mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor) }
2.setShadowLayer只对文字绘制阴影支持硬件加速,对其它的view不支持硬件加速,所以我们需要禁用硬件加速
//关闭单个view的硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
this.setWillNotDraw(false)
3.自定义属性 包括阴影的颜色,模糊半径,偏移距离以及显示的位置
-
自定义属性
<declare-styleable name="ShadowLayout"> <!--阴影颜色--> <attr name="shadowColor" format="color"/> <!--阴影距离--> <attr name="shadowRadius" format="dimension"/> <!--在x轴的阴影--> <attr name="shadowDx" format="dimension"/> <!--在y轴的阴影--> <attr name="shadowDy" format="dimension"/> <!--阴影的方向--> <attr name="shadowSide" format="integer"/> <!--阴影的形状--> <attr name="shadowShape" format="integer"/>
-
读取用户自定义属性
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout) mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor, Color.BLACK) mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, 0f) mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, 0f) mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, 0f) mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, 0) mShadowShape = typedArray.getInt(R.styleable.ShadowLayout_shadowShape, 0) typedArray.recycle()
4.当我们设置阴影的时候,是使用该View去包含某个子View,而阴影是有模糊半径的,所以View需要通过设置Padding来让阴影显示出来,让子View显示我们指定的一个区域内
/**
* 对View进行测量
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val effect = mShadowRadius
var rectLeft = 0f
var rectTop = 0f
var rectRight = this.measuredWidth.toFloat()
var rectBottom = this.measuredHeight.toFloat()
var paddingLeft = 0f
var paddingRight = 0f
var paddingTop = 0f
var paddingBottom = 0f
when (mShadowSide) {
LEFT -> {
rectLeft = effect
paddingLeft = effect
}
RIGHT -> {
rectRight = this.measuredWidth - effect
paddingRight = effect
}
TOP -> {
rectTop = effect
paddingTop = effect
}
BOTTOM -> {
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_RIGHT -> {
rectLeft = effect
paddingLeft = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
}
LEFT_TOP -> {
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
}
LEFT_BOTTOM -> {
rectLeft = effect
paddingLeft = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
RIGHT_TOP -> {
rectRight = this.measuredWidth - effect
paddingRight = effect
rectTop = effect
paddingTop = effect
}
RIGHT_BOTTOM ->{
rectRight = this.measuredWidth - effect
paddingRight = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
TOP_BOTTOM ->{
rectTop = effect
paddingTop = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_TOP_BOTTOM ->{
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_TOP_RIGHT ->{
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
}
RIGHT_LEFT_BOTTOM ->{
rectLeft = effect
paddingLeft = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
else ->{
paddingLeft = effect
paddingRight = effect
paddingTop = effect
paddingBottom = effect
rectLeft = effect
rectRight = this.measuredWidth - effect
rectTop = effect
rectBottom = this.measuredHeight - effect
}
}
mRectF.left = rectLeft
mRectF.right = rectRight
mRectF.top = rectTop
mRectF.bottom = rectBottom
this.setPadding(paddingLeft.toInt(), paddingTop.toInt(), paddingRight.toInt(), paddingBottom.toInt())
}
5.在onDraw()中绘制子View显示的区域以及形状
if (mShadowShape == SHAPE_RECENTAGE) {//绘制长方形
canvas.drawRect(mRectF, mPaint)
} else if (mShadowShape == SHAPE_OVAL) {
canvas.drawCircle(mRectF.centerX(), mRectF.centerY(), Math.min(mRectF.width(), mRectF.height()) / 2, mPaint)
}
6.完整代码
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
class ShadowLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
/**标识符 阴影在布局的显示位置*/
private val LEFT: Int = 0
private val RIGHT: Int = 1
private val TOP: Int = 2
private val BOTTOM: Int = 3
private val LEFT_RIGHT: Int = 4
private val LEFT_TOP: Int = 5
private val LEFT_BOTTOM: Int = 6
private val RIGHT_TOP: Int = 7
private val RIGHT_BOTTOM: Int = 8
private val TOP_BOTTOM: Int = 9
private val LEFT_TOP_BOTTOM: Int = 10
private val LEFT_TOP_RIGHT: Int = 11
private val RIGHT_LEFT_BOTTOM: Int = 12
private val ALL: Int = 13
/**标识符 阴影的形状*/
private val SHAPE_RECENTAGE = 0
private val SHAPE_OVAL = 1
/**阴影的颜色*/
private var mShadowColor: Int = Color.TRANSPARENT
/**阴影的距离*/
private var mShadowRadius: Float = 0.0f
/**阴影在x轴的距离*/
private var mShadowDx: Float = 0.0f
/**阴影在y轴的距离*/
private var mShadowDy: Float = 0.0f
/**阴影的方向*/
private var mShadowSide: Int = ALL
/**阴影的形状*/
private var mShadowShape: Int = SHAPE_RECENTAGE
/**画笔*/
private val mPaint by lazy {
Paint()
}
/***/
private val mRectF: RectF by lazy {
RectF()
}
init {
initAttrs(attrs, context)
initPaint()
}
/**
* 初始化自定义属性
*/
private fun initAttrs(attrs: AttributeSet?, context: Context) {
//关闭单个view的硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
this.setWillNotDraw(false)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout)
mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor, Color.BLACK)
mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, 0f)
mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, 0f)
mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, 0f)
mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, 0)
mShadowShape = typedArray.getInt(R.styleable.ShadowLayout_shadowShape, 0)
typedArray.recycle()
}
/**
* 初始化画笔
*/
private fun initPaint() {
//画笔重置
mPaint.reset()
//设置抗锯齿
mPaint.isAntiAlias = true
//设置透明度
mPaint.color = Color.TRANSPARENT
/**
* 参数1 模糊半径 越大越模糊
* 参数2 阴影在x轴的距离
* 参数3 阴影在y轴的距离
* 参数4 阴影颜色
*/
mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor)
}
/**
* 对View进行测量
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val effect = mShadowRadius
var rectLeft = 0f
var rectTop = 0f
var rectRight = this.measuredWidth.toFloat()
var rectBottom = this.measuredHeight.toFloat()
var paddingLeft = 0f
var paddingRight = 0f
var paddingTop = 0f
var paddingBottom = 0f
when (mShadowSide) {
LEFT -> {
rectLeft = effect
paddingLeft = effect
}
RIGHT -> {
rectRight = this.measuredWidth - effect
paddingRight = effect
}
TOP -> {
rectTop = effect
paddingTop = effect
}
BOTTOM -> {
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_RIGHT -> {
rectLeft = effect
paddingLeft = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
}
LEFT_TOP -> {
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
}
LEFT_BOTTOM -> {
rectLeft = effect
paddingLeft = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
RIGHT_TOP -> {
rectRight = this.measuredWidth - effect
paddingRight = effect
rectTop = effect
paddingTop = effect
}
RIGHT_BOTTOM ->{
rectRight = this.measuredWidth - effect
paddingRight = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
TOP_BOTTOM ->{
rectTop = effect
paddingTop = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_TOP_BOTTOM ->{
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
LEFT_TOP_RIGHT ->{
rectLeft = effect
paddingLeft = effect
rectTop = effect
paddingTop = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
}
RIGHT_LEFT_BOTTOM ->{
rectLeft = effect
paddingLeft = effect
rectRight = this.measuredWidth - effect
paddingRight = effect
rectBottom = this.measuredHeight - effect
paddingBottom = effect
}
else ->{
paddingLeft = effect
paddingRight = effect
paddingTop = effect
paddingBottom = effect
rectLeft = effect
rectRight = this.measuredWidth - effect
rectTop = effect
rectBottom = this.measuredHeight - effect
}
}
mRectF.left = rectLeft
mRectF.right = rectRight
mRectF.top = rectTop
mRectF.bottom = rectBottom
this.setPadding(paddingLeft.toInt(), paddingTop.toInt(), paddingRight.toInt(), paddingBottom.toInt())
}
/**
* 对View进行绘制
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
initPaint()
if (mShadowShape == SHAPE_RECENTAGE) {//绘制长方形
canvas.drawRect(mRectF, mPaint)
} else if (mShadowShape == SHAPE_OVAL) {
canvas.drawCircle(mRectF.centerX(), mRectF.centerY(), Math.min(mRectF.width(), mRectF.height()) / 2, mPaint)
}
}
public fun setShadowRadius(mShadeRadiu: Int) {
this.mShadowRadius = mShadowRadius
//重新对View进行布局
requestLayout()
//在主线程/非主线程进行刷新
postInvalidate()
}
public fun setShadowColor(mShadeColor: Int) {
this.mShadowColor = mShadeColor
//重新对View进行布局
requestLayout()
//在主线程/非主线程进行刷新
postInvalidate()
}
}
7.使用
<view.com.shadowlayoutall.ShadowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="20dp"
app:shadowColor="#12000000"
app:shadowRadius="19dp"
app:shadowShape="0"
app:shadowSide="12">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/white"/>
</view.com.shadowlayoutall.ShadowLayout>