查看: 42|回复: 0

扫光动效

[复制链接]

2

主题

0

回帖

0

积分

积极分子

金币
0
阅读权限
220
精华
0
威望
0
贡献
0
在线时间
0 小时
注册时间
2008-9-25
发表于 2026-4-23 17:11:00 | 显示全部楼层 |阅读模式

使用 LinearGradient 绘制渐变区域,然后旋转角度,可以根据情况跳转扫光区域的大小

针对大量扫光动效同时进行时,需要对绘制进行优化,否则过渡消耗CPU性能

drawRectF.set(
    max(0f, diffX - extraWidth), 0f,
    min(width.toFloat(), lightRectF.right + extraWidth), mHeight
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (!isStart) return
canvas.drawRect(drawRectF, paint)
}
 

只绘制扫光区域,并且对不可见区域不做绘制,暂停时不做绘制

package com.example.screentest

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Matrix
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewOutlineProvider
import android.view.animation.AnticipateOvershootInterpolator
import androidx.core.animation.doOnEnd
import androidx.core.content.withStyledAttributes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin

/**
 * @author liuzhen
 * 流光动效
 * [R.styleable.ShimmerView_src] loading drawable
 * [R.styleable.ShimmerView_shimmerType]see[ShimmerType]
 * [R.styleable.ShimmerView_radius] 圆角
 */
open class ShimmerView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    companion object {
        private const val TAG = "ShimmerView"
        const val SHIMMER_DURATION = 2000L
    }

    private val shimmerColors = intArrayOf(
        Color.TRANSPARENT,
        resources.getColor(R.color.color_shimmer2, context.theme),
        resources.getColor(R.color.color_shimmer1, context.theme),
        resources.getColor(R.color.color_shimmer2, context.theme),
        Color.TRANSPARENT,
    )
    private val positions = floatArrayOf(0f, 0.35f, 0.5f, 0.65f, 1f)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
    private val lightRectF = RectF()
    private val drawRectF = RectF()
    private val mShaderMatrix = Matrix()
    private var diffX = 0f
    private var isStart = false
    private var isInit = false
    private var lightWidth = 0f
    private var extraWidth = 0f
    private var mHeight = 0f
    private var mRotate = 30f
    private var shimmerType = ShimmerType.BANNER.type

    private val shimmerAnimator = ValueAnimator().apply {
        duration = SHIMMER_DURATION
        repeatCount = ValueAnimator.INFINITE
        interpolator = AnticipateOvershootInterpolator(0.3f)
        addUpdateListener { anim ->
            val num = anim.animatedValue as Float
            diffX = num
            updateGradient()
        }
        doOnEnd {
            isStart = false
            diffX = lightWidth
            updateGradient()
        }
    }

    init {
        context.withStyledAttributes(attrs, R.styleable.ShimmerView) {
            val shimmerType = getInt(R.styleable.ShimmerView_shimmerType, ShimmerType.BANNER.type)
            val radius = getDimension(R.styleable.ShimmerView_radius, 0f)
            init(shimmerType, radius)
        }
    }

    fun init(type: Int, radius: Float) {
        shimmerType = type
        clipToOutline = true
        outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outline.setRoundRect(0, 0, measuredWidth, measuredHeight, radius)
            }
        }
    }

    fun startShimmer() {
        if (shimmerAnimator.isStarted || shimmerAnimator.isRunning || isStart) return
        Log.i(TAG, "startShimmer isInit=$isInit")
        if (isInit) {
            isStart = true
            shimmerAnimator.setFloatValues(-(lightWidth + extraWidth), width + lightWidth)
            shimmerAnimator.start()
        }
    }

    fun stopShimmer() {
        Log.i(TAG, "stopShimmer")
        shimmerAnimator.cancel()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 根据旋转角度计算合适的绘制区域宽度
        val rotationRadians = Math.toRadians(mRotate.toDouble())
        val cosValue = abs(cos(rotationRadians))
        val sinValue = abs(sin(rotationRadians))

        // 计算旋转后需要的额外宽度,确保覆盖整个可见区域
        extraWidth = (h * sinValue / cosValue).toFloat()
        lightWidth = w / if (shimmerType == ShimmerType.ITEM.type) 1.2f else 4f
        mHeight = h.toFloat()
        isInit = true
        log("onSizeChanged type=$shimmerType,width=$w,height=$h,lightWidth=$lightWidth,extraWidth=$extraWidth")
        if (!isStart) startShimmer()
    }

    private fun updateGradient() {
        mShaderMatrix.reset()
        lightRectF.set(diffX, 0f, diffX + lightWidth, mHeight)
        // 设置可见区域,避免多余消耗
        drawRectF.set(
            max(0f, diffX - extraWidth), 0f,
            min(width.toFloat(), lightRectF.right + extraWidth), mHeight
        )
        paint.shader = LinearGradient(
            lightRectF.left, lightRectF.top,
            lightRectF.right, lightRectF.top,
            shimmerColors, positions,
            Shader.TileMode.CLAMP
        ).apply {
            mShaderMatrix.setRotate(mRotate, lightRectF.centerX(), lightRectF.centerY())
            setLocalMatrix(mShaderMatrix)
        }
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (!isStart) return
        canvas.drawRect(drawRectF, paint)
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        startShimmer()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        Log.i(TAG, "onDetachedFromWindow type=$shimmerType")
        stopShimmer()
    }

    private fun log(str: String) = Log.i(TAG, str)

}

enum class ShimmerType(val type: Int) {

    /** 流光宽度较小 */
    BANNER(0),

    /** 流光宽度较大 */
    ITEM(1),
}
View Code
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.screentest.ShimmerView
        android:id="@+id/shimmer_view1"
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:layout_marginHorizontal="50dp"
        android:layout_marginTop="20dp"
        android:background="@android:color/background_dark"
        app:radius="24dp"
        app:shimmerType="banner" />

    <com.example.screentest.ShimmerView
        android:id="@+id/shimmer_view2"
        android:layout_width="530dp"
        android:layout_height="300dp"
        android:layout_marginHorizontal="50dp"
        android:layout_marginTop="400dp"
        android:background="@android:color/background_dark"
        app:radius="24dp"
        app:shimmerType="item" />

</FrameLayout>
activity_test
<declare-styleable name="ShimmerView">
        <!-- Sets a drawable as the content of this ImageView. -->
        <attr name="src" format="reference|color" />
        <!-- com.example.screentest.ShimmerType -->
        <attr name="shimmerType" format="enum">
            <enum name="banner" value="0" />
            <enum name="item" value="1" />
        </attr>
        <attr name="radius" format="dimension" />
</declare-styleable>
attrs

image

非旋转状态下起始位置绘制

旋转30度后,在起始位置绘制

 



来源:https://www.cnblogs.com/LiuZhen/p/19917206
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关侵权、举报、投诉及建议等,请发 E-mail:qiongdian@foxmail.com

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部