Android 圆角闪光遮照效果

Jul 31, 2020


效果图

效果图 效果图

实现原理

通过设置 xfermode 取光亮与底下的像素的最大亮度,来实现闪光遮照。 实现原理

代码

创建自定义属性

    <declare-styleable name="MyFlashLightingView">
        <attr name="roundedCornerRadius" format="dimension"/>
        <attr name="animDuration" format="float"/>
    </declare-styleable>
属性 描述
roundedCornerRadius 圆角角度,dp
animDuration 闪光从左到右时间,秒,Float

自定义控件


import android.animation.ValueAnimator
import android.animation.ValueAnimator.INFINITE
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View

/**
 * @author Afra55
 * @date 2020/7/30
 * A smile is the best business card.
 * https://developer.android.com/reference/android/graphics/PorterDuff.Mode
 */
class MyFlashLightingView : View {
    constructor(context: Context) : super(context) {
        init(null, 0)
    }

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

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

    var flashPaint: Paint? = null

    /**
     * 圆角大小,px
     */
    var roundedCornerSize = 0F
    var gradient: LinearGradient? = null
    var gradientMatrix: Matrix? = null
    var rect: RectF? = null

    /**
     * 动画,用来让闪光移动
     */
    var valueAnimator: ValueAnimator? = null

    /**
     * 动画时间,秒
     */
    var animDuration = 3F

    private fun init(attrs: AttributeSet?, defStyle: Int) {

        val a = context.obtainStyledAttributes(
            attrs, R.styleable.MyFlashLightingView, defStyle, 0
        )

        roundedCornerSize = a.getDimension(
            R.styleable.MyFlashLightingView_roundedCornerRadius,
            roundedCornerSize
        )

        animDuration = a.getFloat(R.styleable.MyFlashLightingView_animDuration, animDuration)

        a.recycle()

        flashPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        gradientMatrix = Matrix()
        rect = RectF()

        valueAnimator = ValueAnimator.ofFloat(0F, 1F)
        valueAnimator?.repeatCount = INFINITE
        valueAnimator?.duration = (animDuration * 1000).toLong()
        valueAnimator?.addUpdateListener {
            try {
                val v = it.animatedValue as Float
                // 通过 matrix translate 移动闪光, 从 -width 移动到 2 * width, 整个移动长度是 3 * width, 起点是 -width, 高度可以不移动
                // TODO: 整个长度按需修改
                gradientMatrix?.setTranslate(-width + width * 3 * v, height * v)
                gradient?.setLocalMatrix(gradientMatrix)
                postInvalidate()
            } catch (e: Exception) {
            }
        }


    }

    fun startAnim() {
        valueAnimator?.start()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)

        // 设置渐变光亮
        gradient = LinearGradient(
            0F,
            0F,
            w / 2F,
            h.toFloat(), // 前面4个参数,渐变角度
            // 一共设置了五种渐变颜色,透明,微白,白,微白,透明
            intArrayOf(0x00ffffff, 0x33ffffff, 0x65ffffff, 0x33ffffff, 0x00ffffff),
            // 五种渐变颜色所在控件的相对位置
            floatArrayOf(0.0f, 0.25f, 0.5f, 0.75f, 1F),
            Shader.TileMode.CLAMP // 如果着色器超出原始边界范围,会复制边缘颜色
        )
        flashPaint?.shader = gradient

        // 设置 xfermode 取光亮与底下的像素的最大值
        flashPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.LIGHTEN)

        //  先把光亮放到控件的左边 -width 到 0  处
        // TODO, 如果控件高度高,因为 CLAMP 的原因,起始和结束的闪光不会移出去,这时候需要再往外挪闪光的位置, 同时整个闪光的移动范围也要增大
        gradientMatrix?.setTranslate(-w.toFloat(), h.toFloat())
        gradient?.setLocalMatrix(gradientMatrix)

        // 绘制光亮的宽高,和控件宽高一致
        rect?.set(0F, 0F, w.toFloat(), h.toFloat())

        if (w > 0) {
            post {
                startAnim()
            }
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        try {
            if (rect != null && flashPaint != null) {
                canvas?.drawRoundRect(
                    rect!!,
                    roundedCornerSize,
                    roundedCornerSize,
                    flashPaint!!
                )
            }
        } catch (e: Exception) {
        }
    }


}

使用方法

它可以盖到任何控件上面:

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/home_3_subscribe"
            android:layout_width="261dp"
            android:layout_gravity="center_horizontal"
            android:layout_height="52dp"
            android:background="@drawable/start_now_bg"
            android:gravity="center"
            android:text="Start now"
            android:textColor="#FFFFFF"
            android:textSize="18sp" />

        <com.xxx.xxx.MyFlashLightingView
            android:layout_width="261dp"
            android:layout_gravity="center_horizontal"
            app:roundedCornerRadius="8dp"
            app:animDuration="2"
            android:layout_height="52dp"/>
    </FrameLayout>

PorterDuff.Mode 简介

https://developer.android.com/reference/android/graphics/PorterDuff.Mode

示例图

SRC 图: SRC

DST 图: DST

代码示例

 Paint paint = new Paint();
 // DST 图
 canvas.drawBitmap(destinationImage, 0, 0, paint);

 PorterDuff.Mode mode = // choose a mode
 paint.setXfermode(new PorterDuffXfermode(mode));

// SRC 图
 canvas.drawBitmap(sourceImage, 0, 0, paint);
 

PorterDuff.Mode 效果图示

1. ADD

ADD

2. CLEAR

CLEAR

3. DARKEN

DARKEN

4. DST

DST

5. DST_ATOP

DST_ATOP

6. DST_IN

DST_IN

7. DST_OUT

DST_OUT

8. DST_OVER

DST_OVER

9. LIGHTEN

LIGHTEN

10. MULTIPLY

MULTIPLY

11. OVERLAY

OVERLAY

12. SCREEN

SCREEN

13. SRC

SRC

14. SRC_ATOP

SRC_ATOP

15. SRC_IN

SRC_IN

16. SRC_OUT

SRC_OUT

17. SRC_OVER

SRC_OVER

18. XOR

XOR