Android 自定义 View 实现汽车灯光距离调节动画

在汽车控制类应用中,灯光距离调节是一个常见的交互场景 —— 用户通过调节滑块或按钮,实时看到前灯光照范围的变化,直观反馈操作效果。本文将基于实际项目代码,详解如何通过自定义 View 实现这一效果,包括光照区域绘制、平滑动画过渡及性能优化等核心技术点。

一、功能需求与效果展示

需求分析

汽车前灯的光照范围调节需要实现:

  • 显示前灯的光照区域(含渐变效果,模拟真实灯光衰减)
  • 支持 5 种光照距离(从近到远,光照范围逐渐扩大)
  • 切换距离时,光照边缘需平滑动画过渡
  • 光照区域需有边框线,增强视觉边界感

最终效果

光照区域随调节等级变化,从近到远逐渐扩大,切换时有流畅的动画,光照颜色从光源处向外逐渐变淡(模拟真实灯光衰减)。
在这里插入图片描述

二、核心实现:自定义 CarHeadLightView

自定义 View 是实现这一效果的核心,需处理形状绘制、渐变效果、属性动画三大核心问题。

2.1 基础结构与初始化

自定义 View 的基础结构包括属性定义、画笔初始化、关键数据准备(如光照区域的顶点坐标):

// 光照区域画笔(填充渐变)
    private val mLightPaint by lazyPaint {
        style = Paint.Style.FILL
        isAntiAlias = true // 抗锯齿,避免边缘毛刺
    }

    // 边框画笔(边框渐变)
    private val mBorderLinePaint by lazyPaint {
        style = Paint.Style.STROKE
        strokeWidth = 2f // 边框宽度
        isAntiAlias = true
    }

    // 光照区域路径(核心:定义光照的形状)
    private val mPath by lazy { Path() }

    // 光照区域的顶点集合(动态更新,决定光照范围)
    private var mCurrentPoints = mutableListOf<PointF>()

    // 存储5种光照距离对应的顶点坐标(key:等级0-4,value:顶点集合)
    private val mAllPointsArray: SparseArray<List<PointF>> = SparseArray<List<PointF>>().apply {
        put(0, listOf(PointF(1329.6f, 464f), PointF(837.2f, 464f))) // 最近距离
        put(1, listOf(PointF(1314.6f, 464f), PointF(765.3f, 464f)))
        put(2, listOf(PointF(1301.4f, 464f), PointF(678.3f, 464f)))
        put(3, listOf(PointF(1279.2f, 464f), PointF(518.4f, 464f)))
        put(4, listOf(PointF(1258.8f, 464f), PointF(333.9f, 464f))) // 最远距离
    }

    // 固定的光源起点(前灯位置,不随距离变化)
    private val initPoints = listOf(PointF(1228.2f, 226.4f), PointF(1395.4f, 283.4f))

    init {
        // 开启硬件加速,提升绘制性能
        setLayerType(LAYER_TYPE_HARDWARE, null)
        // 初始化光照区域的弧形路径(仅计算一次,缓存结果)
        mArcPathParams = buildArcPath()
    }

    // 懒加载画笔的工具方法
    private inline fun lazyPaint(crossinline block: Paint.() -> Unit): Lazy<Paint> =
        lazy { Paint().apply(block) }
  • 光照区域由多个顶点定义,其中initPoints是固定的光源位置(前灯所在点),mAllPointsArray存储不同距离对应的终点(光照最远点),通过组合这些点形成完整的光照范围。
  • 使用lazy初始化画笔,避免重复创建;通过setLayerType(LAYER_TYPE_HARDWARE)开启硬件加速,适合频繁绘制的场景。

三、光照区域绘制:Path 与渐变效果

光照区域的视觉效果是核心,需要通过Path构建不规则形状,并结合渐变 shader 模拟灯光衰减。

3.1 构建光照区域的路径(Path)

汽车前灯的光照区域通常是 “扇形” 或 “类梯形”,由固定的光源点和可变的终点连接而成,且顶部边缘为弧形(模拟灯光的扩散效果)。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    if (mCurrentPoints.size < 4) { // 至少需要4个点(2个光源点+2个终点)
        return
    }
    drawLightArea(canvas) // 绘制光照填充区域
    drawBorderLine(canvas) // 绘制边框线
}

/** 绘制光照填充区域 */
private fun drawLightArea(canvas: Canvas) {
    mPath.apply {
        reset()
        // 从第一个光源点开始
        moveTo(mCurrentPoints[0].x, mCurrentPoints[0].y)
        // 绘制顶部弧形边缘(连接两个光源点的弧形)
        val (startAngle, sweepAngle) = mArcPathParams?.first ?: return
        val rect = mArcPathParams?.second ?: return
        arcTo(rect, startAngle, sweepAngle)
        // 连接到第一个终点,再连接到第二个终点,最后闭合路径
        lineTo(mCurrentPoints[2].x, mCurrentPoints[2].y)
        lineTo(mCurrentPoints[3].x, mCurrentPoints[3].y)
        close() // 闭合路径(自动连接回起点)
    }
    // 用带渐变的画笔绘制路径
    canvas.drawPath(mPath, mLightPaint)
}

/** 计算弧形路径的参数(圆心、半径、角度) */
private fun buildArcPath(): Pair<FloatArray, RectF>? {
    // 基于固定光源点计算弧形的圆心、半径、起始角度
    val (centerX, centerY, radius) = calculateArcParams() ?: return null
    val startAngle = calculateStartAngle(centerX, centerY) // 起始角度
    val sweepAngle = calculateSweepAngle(centerX, centerY) // 扫过的角度
    // 弧形所在的矩形(用于arcTo方法)
    val rect = RectF(
        centerX - radius,
        centerY - radius,
        centerX + radius,
        centerY + radius
    )
    return Pair(floatArrayOf(startAngle, sweepAngle), rect)
}

/** 计算弧形的圆心和半径(基于3个点:2个光源点+1个中间点) */
private fun calculateCenterAndRadius(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float): FloatArray {
    // 基于三点求圆的数学公式(计算圆心(xc,yc)和半径)
    val ma = (y2 - y1) / (x2 - x1) // 第一条线的斜率
    val mb = (y3 - y2) / (x3 - x2) // 第二条线的斜率
    // 计算圆心x坐标
    val xc = (ma * mb * (y1 - y3) + mb * (x1 + x2) - ma * (x2 + x3)) / (2 * (mb - ma))
    // 计算圆心y坐标
    val yc = -1 * (xc - (x1 + x2) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值