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一般需要搭配自定义绘制来处理 因为他需要设置给画笔,比较复杂的渐变效果可以尝试这个. 码字不容易,路过的留个赞吧

相关推荐
何盖(何松影)1 小时前
Android T startingwindow使用总结
android
小李飞飞砖2 小时前
Android 依赖注入框架详解
android
SUNxuetian2 小时前
【Android Studio】升级AGP-8.6.1,Find Usage对Method失效的处理方法!
android·ide·gradle·android studio·安卓
阿华的代码王国3 小时前
【Android】搭配安卓环境及设备连接
android·java
__water3 小时前
RHA《Unity兼容AndroidStudio打Apk包》
android·unity·jdk·游戏引擎·sdk·打包·androidstudio
一起搞IT吧5 小时前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@6 小时前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组7 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19968 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸8 小时前
Flutter 生命周期完全指南
android·flutter·ios