实现 Android 的 ShadowLayout

Android 里面并没有带阴影相关的参数控件之类,特别是能满足设计师要求的...
于是实现了一个 ShadowLayout 继承自 FrameLayout,用 ShadowLayout 作为布局的父布局就好了。
首先 阴影 有几个比较重要的参数,偏移量x 偏移量y 圆角 模糊度 阴影颜色
主要利用 paint.setShadowLayer 方法。

建立自定义 View 属性值

//attrs.xml

        
        
        
        
        
        
 
class ShadowLayout : FrameLayout {

    var mShadowColor: Int = 0
    var mShadowRadius: Float = 0.toFloat()
    var mCornerRadius: Float = 0.toFloat()
    var mDx: Float = 0.toFloat()
    var mDy: Float = 0.toFloat()
    var mBackgroundColor: Int = 0

    private var mInvalidateShadowOnSizeChanged = true
    private var mForceInvalidateShadow = false

    constructor(context: Context) : super(context) {
        initView(context, null)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initView(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initView(context, attrs)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (w > 0 && h > 0 && (background == null || mInvalidateShadowOnSizeChanged || mForceInvalidateShadow)) {
            mForceInvalidateShadow = false
            setBackgroundCompat(w, h)
        }
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (mForceInvalidateShadow) {
            mForceInvalidateShadow = false
            setBackgroundCompat(right - left, bottom - top)
        }
    }

    fun setInvalidateShadowOnSizeChanged(invalidateShadowOnSizeChanged: Boolean) {
        mInvalidateShadowOnSizeChanged = invalidateShadowOnSizeChanged
    }

    fun invalidateShadow() {
        mForceInvalidateShadow = true
        background = null
        requestLayout()
        invalidate()
    }

    private fun initView(context: Context, attrs: AttributeSet?) {
        initAttributes(context, attrs)
        refreshPadding()
    }

    fun refreshPadding() {
        val xPadding = (mShadowRadius + Math.abs(mDx)).toInt()
        val yPadding = (mShadowRadius + Math.abs(mDy)).toInt()
        setPadding(xPadding, yPadding, xPadding, yPadding)
    }

    private fun setBackgroundCompat(w: Int, h: Int) {
        val bitmap = createShadowBitmap(w, h, mCornerRadius, mShadowRadius, mDx, mDy, mShadowColor, Color.TRANSPARENT)
        val drawable = BitmapDrawable(resources, bitmap)
        background = drawable
    }


    private fun initAttributes(context: Context, attrs: AttributeSet?) {
        val attr = getTypedArray(context, attrs, R.styleable.ShadowLayout) ?: return
        try {
            mCornerRadius = attr.getDimension(R.styleable.ShadowLayout_shadow_layout_radius, 0f)
            mShadowRadius = attr.getDimension(R.styleable.ShadowLayout_shadow_layout_blur, 0f)
            mDx = attr.getDimension(R.styleable.ShadowLayout_shadow_layout_offsetX, 0f)
            mDy = attr.getDimension(R.styleable.ShadowLayout_shadow_layout_offsetY, 0f)
            mShadowColor = attr.getColor(R.styleable.ShadowLayout_shadow_layout_color, Color.parseColor("#22000000"))
            mBackgroundColor = attr.getColor(R.styleable.ShadowLayout_shadow_layout_background_color, Integer.MIN_VALUE)
        } finally {
            attr.recycle()
        }
    }

    private fun getTypedArray(context: Context, attributeSet: AttributeSet?, attr: IntArray): TypedArray? {
        return context.obtainStyledAttributes(attributeSet, attr, 0, 0)
    }

    private fun createShadowBitmap(shadowWidth: Int, shadowHeight: Int, cornerRadius: Float, shadowRadius: Float,
                                   dx: Float, dy: Float, shadowColor: Int, fillColor: Int): Bitmap {
        val output: Bitmap = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(output)

        val shadowRect = RectF(
                shadowRadius,
                shadowRadius,
                shadowWidth - shadowRadius,
                shadowHeight - shadowRadius)

        if (dy > 0) {
            shadowRect.top += dy
            shadowRect.bottom -= dy
        } else if (dy < 0) {
            shadowRect.top += Math.abs(dy)
            shadowRect.bottom -= Math.abs(dy)
        }

        if (dx > 0) {
            shadowRect.left += dx
            shadowRect.right -= dx
        } else if (dx < 0) {
            shadowRect.left += Math.abs(dx)
            shadowRect.right -= Math.abs(dx)
        }

        val paint = Paint()
        paint.isAntiAlias = true
        paint.color = fillColor
        paint.style = Paint.Style.FILL

        paint.setShadowLayer(shadowRadius, dx, dy, shadowColor)
        canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, paint)
        if (mBackgroundColor != Integer.MIN_VALUE) {
            paint.clearShadowLayer()
            paint.color = mBackgroundColor
            val backgroundRect = RectF(paddingLeft.toFloat(), paddingTop.toFloat(), (width - paddingRight).toFloat(), (height - paddingBottom).toFloat())
            canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, paint)
        }

        return output
    }

}

这里把阴影生成之后设置在背景里面,这样能减少 UI 绘制阴影的次数,特别在列表里面,比起在 draw 里面每次绘制,会流畅很多...

效果图 (随便找了公司产品的页面....)

图1

图2

你可能感兴趣的:(实现 Android 的 ShadowLayout)