
使用 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), }
<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>
<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>

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

旋转30度后,在起始位置绘制
浙公网安备 33010602011771号