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