Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等

继续各式各样的渐变来了,UI大手一挥,码农搞断腿.今天继续渐变色梳理.上篇文章简单的梳理了一下,渐变色的实现方式,今天具体实现一下各式各样的渐变色

1.渐变文字

通过 LinearGradient 设置画笔的Shader 来实现文字的渐变

调用

kotlin 复制代码
// 设置文字渐变

val span = getGradientSpan("我是文字渐变",startColor,endColor,true)
binding.tvText.setText(span, TextView.BufferType.SPANNABLE)


/**
* content 内容
 * startColor 开始颜色
 * endColor 结束颜色
 * isLeftToRight 是否从左到右
 */
private fun getGradientSpan(content: String, startColor: Int, endColor: Int, isLeftToRight: Boolean): SpannableStringBuilder {
    val spannableStringBuilder = SpannableStringBuilder(content)
    val span = KtLinearGradientFontSpan(startColor, endColor, isLeftToRight)
    spannableStringBuilder.setSpan(span, 0, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    return spannableStringBuilder
}

代码

kotlin 复制代码
/**
 * @Author: wkq
 * @Time: 2025/2/11 10:45
 * @Desc:
 */
class KtLinearGradientFontSpan : ReplacementSpan {
    // 文字宽度
    private var mSize = 0

    // 渐变开始颜色
    private var startColor = Color.BLUE

    // 渐变结束颜色
    private var endColor = Color.RED

    // 是否左右渐变
    private var isLeftToRight = true

    constructor()

    constructor(startColor: Int, endColor: Int, leftToRight: Boolean=false) {
        this.startColor = startColor
        this.endColor = endColor
        this.isLeftToRight = leftToRight
    }

    override fun getSize(
        paint: Paint, text: CharSequence, start: Int, end: Int, fm: FontMetricsInt?
    ): Int {
        mSize = paint.measureText(text, start, end).toInt()
        return mSize
    }

    override fun draw(
        canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int,
        bottom: Int, paint: Paint
    ) {
        // 修改y1的值从上到下渐变, 修改x1的值从左到右渐变
        val lg = if (isLeftToRight) {
            LinearGradient(
                0f, 0f, mSize.toFloat(), 0f,
                startColor,
                endColor,
                Shader.TileMode.REPEAT
            )
        } else {
            LinearGradient(
                0f, 0f, 0f, paint.descent() - paint.ascent(),
                startColor,
                endColor,
                Shader.TileMode.REPEAT
            )
        }
        paint.setShader(lg)

        canvas.drawText(text, start, end, x, y.toFloat(), paint) //绘制文字
    }

    fun setLeftToRight(leftToRight: Boolean) {
        isLeftToRight = leftToRight
    }

    fun setEndColor(endColor: Int) {
        this.endColor = endColor
    }

    fun setStartColor(startColor: Int) {
        this.startColor = startColor
    }
}

2.背景渐变

通过 LinearGradient 给画笔设置一个Shader 然后绘制背景

调用

scss 复制代码
 val drawable = GradientBorderDrawable(borderWidth = dp2px(1f).toFloat(), cornerRadius = dp2px(15f).toFloat(), borderColors = colors, backgroundColor = Color.WHITE, orientation = GradientBorderDrawable.Orientation.TOP_BOTTOM)
binding.tvText3.background = drawable

代码

kotlin 复制代码
package com.wkq.tools.ui.view

import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.annotation.IntDef

/**
 * 渐变色包边的 Drawable,支持设置背景色或背景渐变
 */
class GradientBorderDrawable(
    private val borderWidth: Float,
    private val cornerRadius: Float,
    private val borderColors: IntArray,
    @Orientation private val orientation: Int = Orientation.LEFT_RIGHT,
    private val locations: FloatArray? = null,
    private val backgroundColor: Int = Color.TRANSPARENT, // 新增:默认背景透明
    private val backgroundColors: IntArray? = null, // 新增:背景渐变颜色
    private val backgroundOrientation: Int = orientation // 新增:背景渐变方向
) : Drawable() {

    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = borderWidth
    }

    // 新增:背景填充画笔
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    // 绘制区域
    private val rectF = RectF()
    // 边框路径
    private val borderPath = Path()
    // 背景路径(考虑边框宽度)
    private val backgroundPath = Path()

    override fun draw(canvas: Canvas) {
        // 计算背景绘制区域(考虑边框宽度)
        val backgroundRect = RectF(
            borderWidth,
            borderWidth,
            bounds.width().toFloat() - borderWidth,
            bounds.height().toFloat() - borderWidth
        )

        // 绘制背景
        if (backgroundColors != null && backgroundColors.size > 1) {
            // 使用渐变背景
            val backgroundShader = createLinearGradient(
                backgroundRect,
                backgroundColors,
                locations,
                backgroundOrientation
            )
            backgroundPaint.shader = backgroundShader
            backgroundPaint.color = Color.TRANSPARENT // 清除可能的纯色设置
        } else {
            // 使用纯色背景
            backgroundPaint.shader = null
            backgroundPaint.color = backgroundColor
        }

        // 创建背景路径(带圆角)
        backgroundPath.reset()
        backgroundPath.addRoundRect(
            backgroundRect,
            cornerRadius,
            cornerRadius,
            Path.Direction.CW
        )

        // 绘制背景
        canvas.drawPath(backgroundPath, backgroundPaint)

        // 计算边框绘制区域
        rectF.set(
            borderWidth / 2,
            borderWidth / 2,
            bounds.width().toFloat() - borderWidth / 2,
            bounds.height().toFloat() - borderWidth / 2
        )

        // 创建边框渐变
        val shader = createLinearGradient(rectF, borderColors, locations, orientation)
        borderPaint.shader = shader

        // 创建边框路径
        borderPath.reset()
        borderPath.addRoundRect(rectF, cornerRadius, cornerRadius, Path.Direction.CW)

        // 绘制边框
        canvas.drawPath(borderPath, borderPaint)
    }

    // 辅助方法:创建线性渐变
    private fun createLinearGradient(
        rect: RectF,
        colors: IntArray,
        locations: FloatArray?,
        orientation: Int
    ): Shader {
        val startX: Float
        val startY: Float
        val endX: Float
        val endY: Float

        when (orientation) {
            Orientation.LEFT_RIGHT -> {
                startX = rect.left
                startY = rect.centerY()
                endX = rect.right
                endY = rect.centerY()
            }
            Orientation.RIGHT_LEFT -> {
                startX = rect.right
                startY = rect.centerY()
                endX = rect.left
                endY = rect.centerY()
            }
            Orientation.TOP_BOTTOM -> {
                startX = rect.centerX()
                startY = rect.top
                endX = rect.centerX()
                endY = rect.bottom
            }
            Orientation.BOTTOM_TOP -> {
                startX = rect.centerX()
                startY = rect.bottom
                endX = rect.centerX()
                endY = rect.top
            }
            Orientation.DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM -> {
                startX = rect.left
                startY = rect.top
                endX = rect.right
                endY = rect.bottom
            }
            Orientation.DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP -> {
                startX = rect.left
                startY = rect.bottom
                endX = rect.right
                endY = rect.top
            }
            else -> {
                // 默认从左到右
                startX = rect.left
                startY = rect.centerY()
                endX = rect.right
                endY = rect.centerY()
            }
        }

        return LinearGradient(startX, startY, endX, endY, colors, locations, Shader.TileMode.CLAMP)
    }

    override fun setAlpha(alpha: Int) {
        borderPaint.alpha = alpha
        backgroundPaint.alpha = alpha
        invalidateSelf()
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        borderPaint.colorFilter = colorFilter
        backgroundPaint.colorFilter = colorFilter
        invalidateSelf()
    }

    override fun getOpacity(): Int =
        if (backgroundColor == Color.TRANSPARENT && (backgroundColors == null || backgroundColors.isEmpty()))
            PixelFormat.TRANSLUCENT
        else
            PixelFormat.OPAQUE

    @IntDef(
        Orientation.LEFT_RIGHT, Orientation.RIGHT_LEFT,
        Orientation.TOP_BOTTOM, Orientation.BOTTOM_TOP,
        Orientation.DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM, Orientation.DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP
    )
    @Retention(AnnotationRetention.SOURCE)
    annotation class Orientation {
        companion object {
            const val LEFT_RIGHT = 0
            const val RIGHT_LEFT = 1
            const val TOP_BOTTOM = 2
            const val BOTTOM_TOP = 3
            const val DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM = 4
            const val DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP = 5
        }
    }
}

3.渐变边框实现

通过 LinearGradient 给画笔设置一个Shader +path 绘制边框

调用

scss 复制代码
 val drawable = GradientBorderDrawable(borderWidth = dp2px(1f).toFloat(), cornerRadius = dp2px(15f).toFloat(), borderColors = colors, backgroundColor = Color.WHITE, orientation = GradientBorderDrawable.Orientation.TOP_BOTTOM)
binding.tvText3.background = drawable

代码

kotlin 复制代码
import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.annotation.IntDef

/**
 * 渐变色包边的 Drawable,支持设置背景色或背景渐变
 */
class GradientBorderDrawable(
    private val borderWidth: Float,
    private val cornerRadius: Float,
    private val borderColors: IntArray,
    @Orientation private val orientation: Int = Orientation.LEFT_RIGHT,
    private val locations: FloatArray? = null,
    private val backgroundColor: Int = Color.TRANSPARENT, // 新增:默认背景透明
    private val backgroundColors: IntArray? = null, // 新增:背景渐变颜色
    private val backgroundOrientation: Int = orientation // 新增:背景渐变方向
) : Drawable() {

    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = borderWidth
    }

    // 新增:背景填充画笔
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    // 绘制区域
    private val rectF = RectF()
    // 边框路径
    private val borderPath = Path()
    // 背景路径(考虑边框宽度)
    private val backgroundPath = Path()

    override fun draw(canvas: Canvas) {
        // 计算背景绘制区域(考虑边框宽度)
        val backgroundRect = RectF(
            borderWidth,
            borderWidth,
            bounds.width().toFloat() - borderWidth,
            bounds.height().toFloat() - borderWidth
        )

        // 绘制背景
        if (backgroundColors != null && backgroundColors.size > 1) {
            // 使用渐变背景
            val backgroundShader = createLinearGradient(
                backgroundRect,
                backgroundColors,
                locations,
                backgroundOrientation
            )
            backgroundPaint.shader = backgroundShader
            backgroundPaint.color = Color.TRANSPARENT // 清除可能的纯色设置
        } else {
            // 使用纯色背景
            backgroundPaint.shader = null
            backgroundPaint.color = backgroundColor
        }

        // 创建背景路径(带圆角)
        backgroundPath.reset()
        backgroundPath.addRoundRect(
            backgroundRect,
            cornerRadius,
            cornerRadius,
            Path.Direction.CW
        )

        // 绘制背景
        canvas.drawPath(backgroundPath, backgroundPaint)

        // 计算边框绘制区域
        rectF.set(
            borderWidth / 2,
            borderWidth / 2,
            bounds.width().toFloat() - borderWidth / 2,
            bounds.height().toFloat() - borderWidth / 2
        )

        // 创建边框渐变
        val shader = createLinearGradient(rectF, borderColors, locations, orientation)
        borderPaint.shader = shader

        // 创建边框路径
        borderPath.reset()
        borderPath.addRoundRect(rectF, cornerRadius, cornerRadius, Path.Direction.CW)

        // 绘制边框
        canvas.drawPath(borderPath, borderPaint)
    }

    // 辅助方法:创建线性渐变
    private fun createLinearGradient(
        rect: RectF,
        colors: IntArray,
        locations: FloatArray?,
        orientation: Int
    ): Shader {
        val startX: Float
        val startY: Float
        val endX: Float
        val endY: Float

        when (orientation) {
            Orientation.LEFT_RIGHT -> {
                startX = rect.left
                startY = rect.centerY()
                endX = rect.right
                endY = rect.centerY()
            }
            Orientation.RIGHT_LEFT -> {
                startX = rect.right
                startY = rect.centerY()
                endX = rect.left
                endY = rect.centerY()
            }
            Orientation.TOP_BOTTOM -> {
                startX = rect.centerX()
                startY = rect.top
                endX = rect.centerX()
                endY = rect.bottom
            }
            Orientation.BOTTOM_TOP -> {
                startX = rect.centerX()
                startY = rect.bottom
                endX = rect.centerX()
                endY = rect.top
            }
            Orientation.DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM -> {
                startX = rect.left
                startY = rect.top
                endX = rect.right
                endY = rect.bottom
            }
            Orientation.DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP -> {
                startX = rect.left
                startY = rect.bottom
                endX = rect.right
                endY = rect.top
            }
            else -> {
                // 默认从左到右
                startX = rect.left
                startY = rect.centerY()
                endX = rect.right
                endY = rect.centerY()
            }
        }

        return LinearGradient(startX, startY, endX, endY, colors, locations, Shader.TileMode.CLAMP)
    }

    override fun setAlpha(alpha: Int) {
        borderPaint.alpha = alpha
        backgroundPaint.alpha = alpha
        invalidateSelf()
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        borderPaint.colorFilter = colorFilter
        backgroundPaint.colorFilter = colorFilter
        invalidateSelf()
    }

    override fun getOpacity(): Int =
        if (backgroundColor == Color.TRANSPARENT && (backgroundColors == null || backgroundColors.isEmpty()))
            PixelFormat.TRANSLUCENT
        else
            PixelFormat.OPAQUE

    @IntDef(
        Orientation.LEFT_RIGHT, Orientation.RIGHT_LEFT,
        Orientation.TOP_BOTTOM, Orientation.BOTTOM_TOP,
        Orientation.DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM, Orientation.DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP
    )
    @Retention(AnnotationRetention.SOURCE)
    annotation class Orientation {
        companion object {
            const val LEFT_RIGHT = 0
            const val RIGHT_LEFT = 1
            const val TOP_BOTTOM = 2
            const val BOTTOM_TOP = 3
            const val DIAGONAL_LEFT_TOP_TO_RIGHT_BOTTOM = 4
            const val DIAGONAL_LEFT_BOTTOM_TO_RIGHT_TOP = 5
        }
    }
}

4.渐变进度条

通过 LinearGradient 给画笔设置一个Shader 然后绘制一个进度条的背景 再绘制渐变的进度

调用

less 复制代码
binding. arcProgress.setBarBackgroundColor(Color.RED) // 75%
binding. arcProgress.setProgress(0.75f) // 75%
binding. arcProgress.setGradientColors(intArrayOf(Color.parseColor("#FF15EB92"), Color.parseColor("#FF0CC1EA")))

代码

kotlin 复制代码
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View

/**
 *
 *@Author: wkq
 *
 *@Time: 2025/7/2 9:23
 *
 *@Desc:  渐变色进度条
 */
class RoundRectProgressBar @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private var progress = 0f // 0 ~ 1

    private var backgroundColor = Color.LTGRAY
    private var cornerRadius = 30f
    private var gradientColors = intArrayOf(Color.RED, Color.YELLOW, Color.GREEN)

    private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = backgroundColor
    }

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    fun setProgress(value: Float) {
        progress = value.coerceIn(0f, 1f)
        invalidate()
    }

    fun setGradientColors(colors: IntArray) {
        gradientColors = colors
        invalidate()
    }


    /** 设置背景颜色 */
    fun setBarBackgroundColor(color: Int) {
        bgPaint.color = color
        invalidate()
    }

    /** 设置圆角半径 */
    fun setCornerRadius(radius: Float) {
        cornerRadius = radius
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // 1. 背景圆角矩形
        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, bgPaint)

        // 2. 进度条区域
        val progressWidth = width * progress
        if (progressWidth > 0) {
            // 渐变填充
            val shader = LinearGradient(
                0f, 0f, progressWidth, 0f,
                gradientColors, null, Shader.TileMode.CLAMP
            )
            progressPaint.shader = shader

            val progressRect = RectF(0f, 0f, progressWidth, height.toFloat())
            canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressPaint)
        }
    }
}

5.圆形渐变小图标 加文字

通过 LinearGradient 实现渐变色+draw绘制圆形+draw 文字

调用

scss 复制代码
var tip =  CircleNumberDrawableUtil.createGradientCircleDrawable(this,
    dp2px(50f).toFloat() ,
    intArrayOf(startColor,endColor),
    GradientOrientation.TOP_BOTTOM,
    "文字",
    16f,
    Color.WHITE)
binding.view3.setBackgroundDrawable(tip)

代码

kotlin 复制代码
/**
 * 创建带背景渐变色的圆形数字 Drawable
 */
fun createGradientCircleDrawable(
    context: Context,
    sizeDp: Float,                        // Drawable 尺寸 dp
    colors: IntArray,                     // 渐变色数组(至少2个颜色)
    orientation: GradientOrientation,     // 渐变方向
    number: String,                       // 中间数字
    textSizeDp: Float,                    // 字体大小 dp
    @ColorInt textColor: Int              // 字体颜色
): Drawable {
    val density = context.resources.displayMetrics.density
    val sizePx = (sizeDp * density).toInt()
    val textSizePx = textSizeDp * density

    return object : Drawable() {
        private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.FILL
        }

        private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = textColor
            textSize = textSizePx
            textAlign = Paint.Align.CENTER
            typeface = Typeface.DEFAULT_BOLD
        }

        override fun draw(canvas: Canvas) {
            // 设置渐变色
            val shader = LinearGradient(
                orientation.startX(sizePx.toFloat()),
                orientation.startY(sizePx.toFloat()),
                orientation.endX(sizePx.toFloat()),
                orientation.endY(sizePx.toFloat()),
                colors,
                null,
                Shader.TileMode.CLAMP
            )
            backgroundPaint.shader = shader

            val cx = bounds.width() / 2f
            val cy = bounds.height() / 2f
            val radius = Math.min(bounds.width(), bounds.height()) / 2f

            // 画渐变背景圆
            canvas.drawCircle(cx, cy, radius, backgroundPaint)

            // 画数字
            val textY = cy - (textPaint.descent() + textPaint.ascent()) / 2f
            canvas.drawText(number, cx, textY, textPaint)
        }

        override fun setAlpha(alpha: Int) {
            backgroundPaint.alpha = alpha
            textPaint.alpha = alpha
        }

        override fun setColorFilter(colorFilter: ColorFilter?) {
            backgroundPaint.colorFilter = colorFilter
            textPaint.colorFilter = colorFilter
        }

        @Deprecated("Deprecated in Java")
        override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

        override fun getIntrinsicWidth(): Int = sizePx
        override fun getIntrinsicHeight(): Int = sizePx
    }
}

总结

LinearGradient 渐变实现了文字 包边 进度以及背景的功能,功能强大,感兴趣的小伙伴可以看一下.但是LinearGradient一般需要搭配自定义绘制来处理 因为他需要设置给画笔,比较复杂的渐变效果可以尝试这个. 码字不容易,路过的留个赞吧

相关推荐
雨白6 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk6 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING7 小时前
RN容器启动优化实践
android·react native
恋猫de小郭9 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker14 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴14 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos