Android动态变化渐变背景

一、项目中效果

github.com/qingfeng194...

二、实现

ValueAnimator + LinearGradient + Matrix平移

  • ValueAnimator:循环生成 0~1 的进度值
  • LinearGradient:创建比视图宽 2 倍的渐变,避免流动时出现断层
  • Matrix 平移:通过进度值控制渐变的水平偏移,实现流动效果

三、示例代码

1、自定义view代码(以粉蓝渐变相融流动效果为例)

kotlin 复制代码
package com.voicerobot.lottie.widget

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.annotation.ColorInt
import androidx.core.content.withStyledAttributes
import com.voicerobot.lottie.R

class GradientBackgroundView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    @ColorInt
    private var colorStart: Int = 0xFFFF4FD8.toInt() //粉色
    @ColorInt
    private var colorEnd: Int = 0xFF3D8BFF.toInt()   //蓝色

    private var shader: LinearGradient? = null
    private val shaderMatrix = Matrix()

    private var animator: ValueAnimator? = null

    private var progress: Float = 0f          // 动画进度(0~1)
    private var intensity: Float = 1f         // 动画强度(0~1,0=无动画)
    private var speedPxPerSec: Float = 180f   // 流动速度(px/秒)

    init {
        // 读取自定义属性(可选)
        context.withStyledAttributes(attrs, R.styleable.GradientBackgroundView) {
            intensity = getFloat(R.styleable.GradientBackgroundView_gbvIntensity, 1f)
            speedPxPerSec = getFloat(R.styleable.GradientBackgroundView_gbvSpeedPxPerSec, 180f)
        }
    }

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

    override fun onDetachedFromWindow() {
        stopAnim()
        super.onDetachedFromWindow()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (w <= 0 || h <= 0) return

        // 创建2倍宽度的渐变(保证流动时无断层)
        val gradientWidth = w * 2f
        shader = LinearGradient(
            0f, 0f,
            gradientWidth, h.toFloat(),
            intArrayOf(colorStart, colorEnd, colorStart), // 首尾同色,循环无断层
            floatArrayOf(0f, 0.5f, 1f),
            Shader.TileMode.CLAMP
        )
        paint.shader = shader
        startAnimIfPossible()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val w = width
        val h = height
        if (w <= 0 || h <= 0) return

        val currentShader = shader ?: return

        // 根据进度平移渐变
        val dx = (w * progress) * intensity
        shaderMatrix.reset()
        shaderMatrix.setTranslate(dx, 0f)
        currentShader.setLocalMatrix(shaderMatrix)

        // 绘制渐变背景
        canvas.drawRect(0f, 0f, w.toFloat(), h.toFloat(), paint)
    }

    /** 自定义渐变颜色 */
    fun setColors(@ColorInt startColor: Int, @ColorInt endColor: Int) {
        colorStart = startColor
        colorEnd = endColor
        requestLayout()
        invalidate()
    }

    /** 设置动画强度(0=静止,1=最大流动) */
    fun setIntensity(value: Float) {
        intensity = value.coerceIn(0f, 1f)
        invalidate()
    }

    /** 设置流动速度(px/秒) */
    fun setSpeedPxPerSec(value: Float) {
        speedPxPerSec = value.coerceAtLeast(10f)
        restartAnim()
    }

    private fun startAnimIfPossible() {
        if (width <= 0 || height <= 0) return
        if (animator?.isStarted == true) return

        // 根据视图宽度和速度计算动画时长
        val w = width.coerceAtLeast(1)
        val durationMs = ((w / speedPxPerSec) * 1000f).toLong().coerceAtLeast(1200L)

        animator = ValueAnimator.ofFloat(0f, 1f).apply {
            duration = durationMs
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.RESTART
            interpolator = LinearInterpolator()
            addUpdateListener {
                progress = it.animatedValue as Float
                invalidate()
            }
            start()
        }
    }

    private fun restartAnim() {
        stopAnim()
        startAnimIfPossible()
    }

    private fun stopAnim() {
        animator?.cancel()
        animator = null
    }
}

2、自定义属性

res/values/attrs.xml 中添加自定义属性

xml 复制代码
<resources>
<declare-styleable name="GradientBackgroundView">
<!-- 动画强度(0~1) -->
<attr name="gbvIntensity" format="float" />
<!-- 流动速度(px/秒) -->
<attr name="gbvSpeedPxPerSec" format="float" />
</declare-styleable>
</resources>

四、使用

1、在布局中直接引用

ini 复制代码
<com.voicerobot.lottie.widget.GradientBackgroundView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:gbvIntensity="0.8"
app:gbvSpeedPxPerSec="200" />

2、代码中动态配置

scss 复制代码
// 自定义颜色

gradientView.setColors(0xFF00FF00.toInt(), 0xFFFF0000.toInt())

// 调整动画强度(比如滑动时减弱动画)

gradientView.setIntensity(0.5f)

// 调整流动速度

gradientView.setSpeedPxPerSec(250f)

五、关键细节解析

  1. 2 倍宽度渐变 :创建比视图宽 2 倍的渐变,配合首尾同色(colorStart -> colorEnd -> colorStart),保证流动时无断层。
  2. 动画时长自适应:根据视图宽度和速度(px / 秒)计算动画时长,不同尺寸的视图流动速度一致。
  3. 生命周期管理 :在onAttachedToWindow启动动画、onDetachedFromWindow停止动画,避免内存泄漏。
  4. 性能优化 :仅在视图尺寸变化时重建渐变,动画仅触发invalidate()更新绘制,性能开销极低。
相关推荐
通玄11 分钟前
Jetpack Compose 入门系列(十):Paging 3 分页加载
android
hunterandroid17 分钟前
DataStore 工程化实践:迁移、并发更新与异常恢复
android·前端
程序员-珍2 小时前
报错下载android sdk失败
android·java
齊家治國平天下4 小时前
AAOS 电源管理深度解析:休眠/唤醒/功耗优化
android·车载系统·aaos·aosp·电源管理·休眠唤醒·carpowermanager
Xzaveir4 小时前
企业号码展示不可观测怎么办:状态机、拨测事件与异常回放
android·人工智能
2501_916007474 小时前
iOS和macOS应用程序性能分析和优化工具使用综合指南
android·macos·ios·小程序·uni-app·iphone·webview
黄林晴5 小时前
Nearby Connections 重大变更:不再自动开启 Wi-Fi / 蓝牙,适配指南
android
恋猫de小郭5 小时前
Flutter 全新真 3D 实现,用 flutter_scene 能开发一个「我的世界」
android·前端·flutter
HLC++18 小时前
Linux的进程间通信
android·linux·服务器
爱笑鱼1 天前
Binder(二):AIDL 生成的 Proxy、Stub 和 Parcel 到底在做什么?
android