Android进阶之路 - TextView文本渐变

那天做需求的时候,遇到一个小功能,建立在前人栽树,后人乘凉的情况下,仅用片刻就写完了;说来惭愧,我以前并未写过文本渐变的需求,脑中也仅有一个shape渐变带来的大概思路,回头来看想着学习一下这款自定义控件的内部实现,故记录于此

很多时候通过阅读原作者源码,总能为我们带来一些思考,一些成长

Tip:为表尊重,源码中的注释声明并未做任何修改,仅记录自身学习中的思考、想法

    • 效果
      • 需求效果
      • 实现效果
    • 基础思考
    • 开发实践
      • 项目结构
      • 使用方式
    • 集成学习
      • ShapeTextView 自定义控件
      • shape_attr 自定义属性
      • Styleable 动态属性
        • IShapeDrawableStyleable 背景属性抽象类
        • ITextColorStyleable 文本属性抽象类
        • ShapeTextViewStyleable 具体实现类
      • Builder
        • ShapeDrawableBuilder
        • TextColorBuilder
      • LinearGradientFontSpan 文本渐变核心类

效果

可以先看看效果是不是你所需要的,以免浪费开发时间… 如有需要可直接 下载Demo

需求效果

Android进阶之路 - TextView文本渐变_第1张图片

渐变背景

Android进阶之路 - TextView文本渐变_第2张图片

渐变文本

Android进阶之路 - TextView文本渐变_第3张图片

实现效果

本文只针对item样式中的 右上角的双重渐变(渐变背景、渐变文本)实现
在这里插入图片描述


基础思考

如果让你实现右上角的标签,你考虑了哪些实现方式?

Tip:右上角标签仅可能有一个,只是样式、描述不同

  • 单标签固定样式:产品要求不严格的话,直接让设计切图!(简单便捷)
  • 多标签固定样式:产品要求不严格的话,当样式标签固定在 2-5个之间,可以让设计切图,显示逻辑处理!(简单便捷)
  • 多标签不固定样式:例如内部可能字体可能是精选、常态、盈利等等,当这种场景被不确定占据时,只能通过代码来进行适配

关于渐变背景,因仅为一种固定渐变背景,且并非本文关键,故在此直接写出,如果你想更详细的学习和了解shape,可前往 shape保姆级手册

关于渐变位置主要有startColorcenterColorendColor ,如根据设计图的话,可仅设置startColorendColor

shape_staid_select_top_right(背景shape)- Demo中可能未打包


<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners
        android:bottomLeftRadius="5dp"
        android:topRightRadius="5dp" />

    <padding
        android:bottom="1dp"
        android:left="@dimen/mp_12"
        android:right="@dimen/mp_12"
        android:top="1dp" />

    <gradient
        android:angle="45"
        android:centerColor="#F8E2C7"
        android:endColor="#F8E2C8"
        android:startColor="#F8E2C7" />
shape>

开发实践

因为我们的文本渐变效果是直接从 ShapeView 剥离的产物,所以如果不介意引入三方库的话,最简单的方式肯定是直接依赖了,只不过会导致项目体积更大一些,毕竟有些东西我们用不到

关于 ShapeView 的集成,可自行前往作者项目主页查看,此处仅做部分摘要
Android进阶之路 - TextView文本渐变_第4张图片

项目结构

此处项目结构为我Demo的结构目录,主要通过减少三方库的引入,从而减少项目体积、包体积

Android进阶之路 - TextView文本渐变_第5张图片

使用方式

ShapeTextView 就是我们这次学习的控件,现在开始一步步倒推看一下


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    tools:context=".MainActivity">

    <com.example.shapefontbg.shape.ShapeTextView
        android:id="@+id/tv_shape"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:background="@drawable/shape_staid_select_top_right"
        android:text="我爱洗澡,皮肤好好~"
        android:textSize="14sp"
        app:shape_textEndColor="#501512"
        app:shape_textStartColor="#AD5C22"
        app:typefaceScale="medium" />

RelativeLayout>

集成学习

ShapeTextView 自定义控件

我感觉自定义控件内主要有以下几点,需要总结、注意

  • 初始化背景属性、文本属性(内部)
  • 通过读取typefaceScale属性,设置对应字体加粗效果(内部)
  • 增添动态设置TextsetTextColorTypefaceScale方法(支持外部调用)
package com.example.shapefontbg.shape

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.example.shapefontbg.R
import com.example.shapefontbg.shape.builder.ShapeDrawableBuilder
import com.example.shapefontbg.shape.builder.TextColorBuilder
import com.example.shapefontbg.shape.styleable.ShapeTextViewStyleable

/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/07/17
 * desc   : 支持直接定义 Shape 背景的 TextView
 */
class ShapeTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    AppCompatTextView(context, attrs, defStyleAttr) {
    private val shapeDrawableBuilder: ShapeDrawableBuilder
    private val textColorBuilder: TextColorBuilder?
    private var typefaceScale: Float

    companion object {
        private val STYLEABLE = ShapeTextViewStyleable()
    }

    enum class TypefaceScale {
        MEDIUM, MEDIUM_SMALL, DEFAULT,
    }

    init {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShapeTextView)
        //初始化背景属性
        shapeDrawableBuilder = ShapeDrawableBuilder(this, typedArray, STYLEABLE)
        //初始化文本属性
        textColorBuilder = TextColorBuilder(this, typedArray, STYLEABLE)
        // 读取字体加粗程度
        val scale = typedArray.getInt(R.styleable.ShapeTextView_typefaceScale, 0)
        typefaceScale = typedArrayTypefaceScale(scale)
        // 资源回收
        typedArray.recycle()
        // 设置相关背景属性
        shapeDrawableBuilder.intoBackground()
        // 设置相关文本属性
//        原始部分        
//        if (textColorBuilder.isTextGradientColors) {
//            text = textColorBuilder.buildLinearGradientSpannable(text)
//        } else {
//            textColorBuilder.intoTextColor()
//        }
        textColorBuilder.intoTextColor()
    }

    /**
    * 字体粗度
    * */
    private fun typedArrayTypefaceScale(scale: Int): Float = when (scale) {
        1 -> 0.6f
        2 -> 1.1f
        else -> 0.0f
    }

    override fun setTextColor(color: Int) {
        super.setTextColor(color)
        textColorBuilder?.textColor = color
        textColorBuilder?.clearTextGradientColors()
    }

    /**
     * 渐变入口
     * */
    override fun setText(text: CharSequence, type: BufferType) {
        if (textColorBuilder?.isTextGradientColors == true) {
            super.setText(textColorBuilder.buildLinearGradientSpannable(text), type)
        } else {
            super.setText(text, type)
        }
    }

    override fun onDraw(canvas: Canvas?) {
        if (typefaceScale == 0f) {
            return super.onDraw(canvas)
        }
        val strokeWidth = paint.strokeWidth
        val style = paint.style
        paint.strokeWidth = typefaceScale
        paint.style = Paint.Style.FILL_AND_STROKE
        super.onDraw(canvas)
        paint.strokeWidth = strokeWidth
        paint.style = style
    }

    fun setTypefaceScale(scale: TypefaceScale = TypefaceScale.DEFAULT) {
        typefaceScale = when (scale) {
            TypefaceScale.DEFAULT -> 0.0f
            TypefaceScale.MEDIUM_SMALL -> 0.6f
            TypefaceScale.MEDIUM -> 1.1f
        }
        invalidate()
    }

}

修改部分

原始

  if (textColorBuilder.isTextGradientColors) {
      text = textColorBuilder.buildLinearGradientSpannable(text)
  } else {
      textColorBuilder.intoTextColor()
  }

改为(可能多走了一层内层判断,性能应该没有太大影响)

 textColorBuilder.intoTextColor()

原因:内部、外部判断逻辑重复,去除外部判断即可

Android进阶之路 - TextView文本渐变_第6张图片

内部实现为文本渐变核心类,后续会单独说明

在这里插入图片描述


shape_attr 自定义属性


<resources>

    
    <attr name="shape">
        
        <enum name="rectangle" value="0" />
        
        <enum name="oval" value="1" />
        
        <enum name="line" value="2" />
        
        <enum name="ring" value="3" />
    attr>
    
    <attr name="shape_width" format="dimension" />
    
    <attr name="shape_height" format="dimension" />

    
    <attr name="shape_solidColor" format="color|reference" />
    
    <attr name="shape_solidPressedColor" format="color|reference" />
    
    <attr name="shape_solidCheckedColor" format="color|reference" />
    
    <attr name="shape_solidDisabledColor" format="color|reference" />
    
    <attr name="shape_solidFocusedColor" format="color|reference" />
    
    <attr name="shape_solidSelectedColor" format="color|reference" />

    
    <attr name="shape_radius" format="dimension" />
    
    <attr name="shape_topLeftRadius" format="dimension" />
    
    <attr name="shape_topRightRadius" format="dimension" />
    
    <attr name="shape_bottomLeftRadius" format="dimension" />
    
    <attr name="shape_bottomRightRadius" format="dimension" />

    
    <attr name="shape_startColor" format="color" />
    
    <attr name="shape_centerColor" format="color" />
    
    <attr name="shape_endColor" format="color" />
    
    <attr name="shape_useLevel" format="boolean" />
    
    <attr name="shape_angle" format="float" />
    
    <attr name="shape_gradientType">
        
        <enum name="linear" value="0" />
        
        <enum name="radial" value="1" />
        
        <enum name="sweep" value="2" />
    attr>
    
    <attr name="shape_centerX" format="float|fraction" />
    
    <attr name="shape_centerY" format="float|fraction" />
    
    <attr name="shape_gradientRadius" format="float|fraction|dimension" />

    
    <attr name="shape_strokeColor" format="color|reference" />
    
    <attr name="shape_strokePressedColor" format="color|reference" />
    
    <attr name="shape_strokeCheckedColor" format="color|reference" />
    
    <attr name="shape_strokeDisabledColor" format="color|reference" />
    
    <attr name="shape_strokeFocusedColor" format="color|reference" />
    
    <attr name="shape_strokeSelectedColor" format="color|reference" />

    
    <attr name="shape_strokeWidth" format="dimension" />
    
    <attr name="shape_dashWidth" format="dimension" />
    
    <attr name="shape_dashGap" format="dimension" />

    
    <attr name="shape_textColor" format="color|reference" />
    
    <attr name="shape_textPressedColor" format="color|reference" />
    
    <attr name="shape_textCheckedColor" format="color|reference" />
    
    <attr name="shape_textDisabledColor" format="color|reference" />
    
    <attr name="shape_textFocusedColor" format="color|reference" />
    
    <attr name="shape_textSelectedColor" format="color|reference" />

    
    <attr name="shape_textStartColor" format="color" />
    
    <attr name="shape_textCenterColor" format="color" />
    
    <attr name="shape_textEndColor" format="color" />
    
    <attr name="shape_textGradientOrientation">
        
        <enum name="horizontal" value="0" />
        
        <enum name="vertical" value="1" />
    attr>

    
    <attr name="shape_buttonDrawable" format="reference" />
    
    <attr name="shape_buttonPressedDrawable" format="reference" />
    
    <attr name="shape_buttonCheckedDrawable" format="reference" />
    
    <attr name="shape_buttonDisabledDrawable" format="reference" />
    
    <attr name="shape_buttonFocusedDrawable" format="reference" />
    
    <attr name="shape_buttonSelectedDrawable" format="reference" />

    <attr name="typefaceScale">
        <enum name="normal" value="0" />
        <enum name="medium_small" value="1" />
        <enum name="medium" value="2" />
    attr>

    <declare-styleable name="ShapeTextView">
        <attr name="shape" />
        <attr name="shape_width" />
        <attr name="shape_height" />
        <attr name="shape_solidColor" />
        <attr name="shape_solidPressedColor" />
        <attr name="shape_solidDisabledColor" />
        <attr name="shape_solidFocusedColor" />
        <attr name="shape_solidSelectedColor" />
        <attr name="shape_radius" />
        <attr name="shape_topLeftRadius" />
        <attr name="shape_topRightRadius" />
        <attr name="shape_bottomLeftRadius" />
        <attr name="shape_bottomRightRadius" />
        <attr name="shape_startColor" />
        <attr name="shape_centerColor" />
        <attr name="shape_endColor" />
        <attr name="shape_useLevel" />
        <attr name="shape_angle" />
        <attr name="shape_gradientType" />
        <attr name="shape_centerX" />
        <attr name="shape_centerY" />
        <attr name="shape_gradientRadius" />
        <attr name="shape_strokeColor" />
        <attr name="shape_strokePressedColor" />
        <attr name="shape_strokeDisabledColor" />
        <attr name="shape_strokeFocusedColor" />
        <attr name="shape_strokeSelectedColor" />
        <attr name="shape_strokeWidth" />
        <attr name="shape_dashWidth" />
        <attr name="shape_dashGap" />

        <attr name="shape_textColor" />
        <attr name="shape_textPressedColor" />
        <attr name="shape_textDisabledColor" />
        <attr name="shape_textFocusedColor" />
        <attr name="shape_textSelectedColor" />

        <attr name="shape_textStartColor" />
        <attr name="shape_textCenterColor" />
        <attr name="shape_textEndColor" />
        <attr name="shape_textGradientOrientation" />

        <attr name="typefaceScale" />

    declare-styleable>
resources>

Styleable 动态属性

分别针对 ShapeView背景TextView自身 通用型自定义属性

我觉得因为原始项目中具体实现Styleable类有很多个,如果后续有扩展的话,也可以再用工厂模式或策略模式二次封装;

Android进阶之路 - TextView文本渐变_第7张图片

IShapeDrawableStyleable 背景属性抽象类
package com.example.shapefontbg.shape.styleable

/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/28
 * desc   : ShapeDrawable View 属性收集接口
 */
interface IShapeDrawableStyleable {
    val shapeTypeStyleable: Int
    val shapeWidthStyleable: Int
    val shapeHeightStyleable: Int
    val solidColorStyleable: Int
    val solidPressedColorStyleable: Int
    val solidCheckedColorStyleable: Int
        get() = 0
    val solidDisabledColorStyleable: Int
    val solidFocusedColorStyleable: Int
    val solidSelectedColorStyleable: Int
    val radiusStyleable: Int
    val topLeftRadiusStyleable: Int
    val topRightRadiusStyleable: Int
    val bottomLeftRadiusStyleable: Int
    val bottomRightRadiusStyleable: Int
    val startColorStyleable: Int
    val centerColorStyleable: Int
    val endColorStyleable: Int
    val useLevelStyleable: Int
    val angleStyleable: Int
    val gradientTypeStyleable: Int
    val centerXStyleable: Int
    val centerYStyleable: Int
    val gradientRadiusStyleable: Int
    val strokeColorStyleable: Int
    val strokePressedColorStyleable: Int
    val strokeCheckedColorStyleable: Int
        get() = 0
    val strokeDisabledColorStyleable: Int
    val strokeFocusedColorStyleable: Int
    val strokeSelectedColorStyleable: Int
    val strokeWidthStyleable: Int
    val dashWidthStyleable: Int
    val dashGapStyleable: Int
}
ITextColorStyleable 文本属性抽象类
package com.example.shapefontbg.shape.styleable

/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/28
 * desc   : 文本颜色 View 属性收集接口
 */
interface ITextColorStyleable {
    val textColorStyleable: Int
    val textPressedColorStyleable: Int
    val textCheckedColorStyleable: Int
        get() = 0
    val textDisabledColorStyleable: Int
    val textFocusedColorStyleable: Int
    val textSelectedColorStyleable: Int
    val textStartColorStyleable: Int
    val textCenterColorStyleable: Int
    val textEndColorStyleable: Int
    val textGradientOrientationStyleable: Int
}
ShapeTextViewStyleable 具体实现类

我感觉主要有俩点作用:

  • 声明对应抽象类的自定义属性,可用于固定属性、动态设置属性
  • 将动态属性和静态自定义属性做了一个基础映射
package com.example.shapefontbg.shape.styleable

import com.example.shapefontbg.R


/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/28
 * desc   : TextView 的 Shape 属性值
 */
class ShapeTextViewStyleable : IShapeDrawableStyleable, ITextColorStyleable {
    /**
     * [IShapeDrawableStyleable]
     */
    override val shapeTypeStyleable = R.styleable.ShapeTextView_shape
    override val shapeWidthStyleable = R.styleable.ShapeTextView_shape_width
    override val shapeHeightStyleable = R.styleable.ShapeTextView_shape_height
    override val solidColorStyleable = R.styleable.ShapeTextView_shape_solidColor
    override val solidPressedColorStyleable = R.styleable.ShapeTextView_shape_solidPressedColor
    override val solidDisabledColorStyleable = R.styleable.ShapeTextView_shape_solidDisabledColor
    override val solidFocusedColorStyleable = R.styleable.ShapeTextView_shape_solidFocusedColor
    override val solidSelectedColorStyleable = R.styleable.ShapeTextView_shape_solidSelectedColor
    override val radiusStyleable = R.styleable.ShapeTextView_shape_radius
    override val topLeftRadiusStyleable = R.styleable.ShapeTextView_shape_topLeftRadius
    override val topRightRadiusStyleable = R.styleable.ShapeTextView_shape_topRightRadius
    override val bottomLeftRadiusStyleable = R.styleable.ShapeTextView_shape_bottomLeftRadius
    override val bottomRightRadiusStyleable = R.styleable.ShapeTextView_shape_bottomRightRadius
    override val startColorStyleable = R.styleable.ShapeTextView_shape_startColor
    override val centerColorStyleable = R.styleable.ShapeTextView_shape_centerColor
    override val endColorStyleable = R.styleable.ShapeTextView_shape_endColor
    override val useLevelStyleable = R.styleable.ShapeTextView_shape_useLevel
    override val angleStyleable = R.styleable.ShapeTextView_shape_angle
    override val gradientTypeStyleable = R.styleable.ShapeTextView_shape_gradientType
    override val centerXStyleable = R.styleable.ShapeTextView_shape_centerX
    override val centerYStyleable = R.styleable.ShapeTextView_shape_centerY
    override val gradientRadiusStyleable = R.styleable.ShapeTextView_shape_gradientRadius
    override val strokeColorStyleable = R.styleable.ShapeTextView_shape_strokeColor
    override val strokePressedColorStyleable = R.styleable.ShapeTextView_shape_strokePressedColor
    override val strokeDisabledColorStyleable = R.styleable.ShapeTextView_shape_strokeDisabledColor
    override val strokeFocusedColorStyleable = R.styleable.ShapeTextView_shape_strokeFocusedColor
    override val strokeSelectedColorStyleable = R.styleable.ShapeTextView_shape_strokeSelectedColor
    override val strokeWidthStyleable = R.styleable.ShapeTextView_shape_strokeWidth
    override val dashWidthStyleable = R.styleable.ShapeTextView_shape_dashWidth
    override val dashGapStyleable = R.styleable.ShapeTextView_shape_dashGap
    /**
     * [ITextColorStyleable]
     */
    override val textColorStyleable = R.styleable.ShapeTextView_shape_textColor
    override val textPressedColorStyleable = R.styleable.ShapeTextView_shape_textPressedColor
    override val textDisabledColorStyleable = R.styleable.ShapeTextView_shape_textDisabledColor
    override val textFocusedColorStyleable = R.styleable.ShapeTextView_shape_textFocusedColor
    override val textSelectedColorStyleable = R.styleable.ShapeTextView_shape_textSelectedColor
    override val textStartColorStyleable = R.styleable.ShapeTextView_shape_textStartColor
    override val textCenterColorStyleable = R.styleable.ShapeTextView_shape_textCenterColor
    override val textEndColorStyleable = R.styleable.ShapeTextView_shape_textEndColor
    override val textGradientOrientationStyleable = R.styleable.ShapeTextView_shape_textGradientOrientation
}

扩展:因为原作者的Shape使用场景、范围较广,所以有挺多Layout-Styleable,内部实现也均有所不同,因为该篇主要记录渐变文本,所以我们仅需关注ShapeTextViewStyleable即可

Android进阶之路 - TextView文本渐变_第8张图片


Builder

关于 ShapeDrawableBuilderTextColorBuilder 的封装剥离,个人认为这样的封装方式主要有以下考虑

  • 单一职责,解耦(分别作用于背景和TextView自身)
  • 支持自定义属性设置方式(含静态设置、动态设置)
  • 建造者模式,便于链式动态设置自定义属性
  • 封装一些通用型方法
ShapeDrawableBuilder

主要作用于Shape背景相关属性设置

package com.example.shapefontbg.shape.builder;

import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.view.View;

import androidx.annotation.Nullable;

import com.example.shapefontbg.shape.styleable.IShapeDrawableStyleable;

/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/28
 * desc   : ShapeDrawable 构建类
 */
@SuppressWarnings("unused")
public final class ShapeDrawableBuilder {

    private static final int NO_COLOR = Color.TRANSPARENT;

    private final View mView;

    private int mShape;
    private int mShapeWidth;
    private int mShapeHeight;

    private int mSolidColor;
    private Integer mSolidPressedColor;
    private Integer mSolidCheckedColor;
    private Integer mSolidDisabledColor;
    private Integer mSolidFocusedColor;
    private Integer mSolidSelectedColor;

    private float mTopLeftRadius;
    private float mTopRightRadius;
    private float mBottomLeftRadius;
    private float mBottomRightRadius;

    private int[] mGradientColors;
    private boolean mUseLevel;
    private int mAngle;
    private int mGradientType;
    private float mCenterX;
    private float mCenterY;
    private int mGradientRadius;

    private int mStrokeColor;
    private Integer mStrokePressedColor;
    private Integer mStrokeCheckedColor;
    private Integer mStrokeDisabledColor;
    private Integer mStrokeFocusedColor;
    private Integer mStrokeSelectedColor;

    private int mStrokeWidth;
    private int mDashWidth;
    private int mDashGap;

    public ShapeDrawableBuilder(View view, TypedArray typedArray, IShapeDrawableStyleable styleable) {
        mView = view;
        mShape = typedArray.getInt(styleable.getShapeTypeStyleable(), 0);
        mShapeWidth = typedArray.getDimensionPixelSize(styleable.getShapeWidthStyleable(), -1);
        mShapeHeight = typedArray.getDimensionPixelSize(styleable.getShapeHeightStyleable(), -1);

        mSolidColor = typedArray.getColor(styleable.getSolidColorStyleable(), NO_COLOR);
        if (typedArray.hasValue(styleable.getSolidPressedColorStyleable())) {
            mSolidPressedColor = typedArray.getColor(styleable.getSolidPressedColorStyleable(), NO_COLOR);
        }
        if (styleable.getSolidCheckedColorStyleable() > 0 && typedArray.hasValue(styleable.getSolidCheckedColorStyleable())) {
            mSolidCheckedColor = typedArray.getColor(styleable.getSolidCheckedColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getSolidDisabledColorStyleable())) {
            mSolidDisabledColor = typedArray.getColor(styleable.getSolidDisabledColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getSolidFocusedColorStyleable())) {
            mSolidFocusedColor = typedArray.getColor(styleable.getSolidFocusedColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getSolidSelectedColorStyleable())) {
            mSolidSelectedColor = typedArray.getColor(styleable.getSolidSelectedColorStyleable(), NO_COLOR);
        }

        int radius = typedArray.getDimensionPixelSize(styleable.getRadiusStyleable(), 0);
        mTopLeftRadius = typedArray.getDimensionPixelSize(styleable.getTopLeftRadiusStyleable(), radius);
        mTopRightRadius = typedArray.getDimensionPixelSize(styleable.getTopRightRadiusStyleable(), radius);
        mBottomLeftRadius = typedArray.getDimensionPixelSize(styleable.getBottomLeftRadiusStyleable(), radius);
        mBottomRightRadius = typedArray.getDimensionPixelSize(styleable.getBottomRightRadiusStyleable(), radius);

        if (typedArray.hasValue(styleable.getStartColorStyleable()) && typedArray.hasValue(styleable.getEndColorStyleable())) {
            if (typedArray.hasValue(styleable.getCenterColorStyleable())) {
                mGradientColors = new int[]{typedArray.getColor(styleable.getStartColorStyleable(), NO_COLOR),
                        typedArray.getColor(styleable.getCenterColorStyleable(), NO_COLOR),
                        typedArray.getColor(styleable.getEndColorStyleable(), NO_COLOR)};
            } else {
                mGradientColors = new int[]{typedArray.getColor(styleable.getStartColorStyleable(), NO_COLOR),
                        typedArray.getColor(styleable.getEndColorStyleable(), NO_COLOR)};
            }
        }

        mUseLevel = typedArray.getBoolean(styleable.getUseLevelStyleable(), false);
        mAngle = (int) typedArray.getFloat(styleable.getAngleStyleable(), 0);
        mGradientType = typedArray.getInt(styleable.getGradientTypeStyleable(), GradientDrawable.LINEAR_GRADIENT);
        mCenterX = typedArray.getFloat(styleable.getCenterXStyleable(), 0.5f);
        mCenterY = typedArray.getFloat(styleable.getCenterYStyleable(), 0.5f);
        mGradientRadius = typedArray.getDimensionPixelSize(styleable.getGradientRadiusStyleable(), radius);

        mStrokeColor = typedArray.getColor(styleable.getStrokeColorStyleable(), NO_COLOR);
        if (typedArray.hasValue(styleable.getStrokePressedColorStyleable())) {
            mStrokePressedColor = typedArray.getColor(styleable.getStrokePressedColorStyleable(), NO_COLOR);
        }
        if (styleable.getStrokeCheckedColorStyleable() > 0 && typedArray.hasValue(styleable.getStrokeCheckedColorStyleable())) {
            mStrokeCheckedColor = typedArray.getColor(styleable.getStrokeCheckedColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getStrokeDisabledColorStyleable())) {
            mStrokeDisabledColor = typedArray.getColor(styleable.getStrokeDisabledColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getStrokeFocusedColorStyleable())) {
            mStrokeFocusedColor = typedArray.getColor(styleable.getStrokeFocusedColorStyleable(), NO_COLOR);
        }
        if (typedArray.hasValue(styleable.getStrokeSelectedColorStyleable())) {
            mStrokeSelectedColor = typedArray.getColor(styleable.getStrokeSelectedColorStyleable(), NO_COLOR);
        }

        mStrokeWidth = typedArray.getDimensionPixelSize(styleable.getStrokeWidthStyleable(), 0);
        mDashWidth = typedArray.getDimensionPixelSize(styleable.getDashWidthStyleable(), 0);
        mDashGap = typedArray.getDimensionPixelSize(styleable.getDashGapStyleable(), 0);

    }

    public ShapeDrawableBuilder setShape(int shape) {
        mShape = shape;
        return this;
    }

    public int getShape() {
        return mShape;
    }

    public ShapeDrawableBuilder setShapeWidth(int width) {
        mShapeWidth = width;
        return this;
    }

    public int getShapeWidth() {
        return mShapeWidth;
    }

    public ShapeDrawableBuilder setShapeHeight(int height) {
        mShapeHeight = height;
        return this;
    }

    public int getShapeHeight() {
        return mShapeHeight;
    }

    public ShapeDrawableBuilder setSolidColor(int color) {
        mSolidColor = color;
        clearGradientColors();
        return this;
    }

    public int getSolidColor() {
        return mSolidColor;
    }

    public ShapeDrawableBuilder setSolidPressedColor(Integer color) {
        mSolidPressedColor = color;
        return this;
    }

    @Nullable
    public Integer getSolidPressedColor() {
        return mSolidPressedColor;
    }

    public ShapeDrawableBuilder setSolidCheckedColor(Integer color) {
        mSolidCheckedColor = color;
        return this;
    }

    @Nullable
    public Integer getSolidCheckedColor() {
        return mSolidCheckedColor;
    }

    public ShapeDrawableBuilder setSolidDisabledColor(Integer color) {
        mSolidDisabledColor = color;
        return this;
    }

    @Nullable
    public Integer getSolidDisabledColor() {
        return mSolidDisabledColor;
    }

    public ShapeDrawableBuilder setSolidFocusedColor(Integer color) {
        mSolidFocusedColor = color;
        return this;
    }

    @Nullable
    public Integer getSolidFocusedColor() {
        return mSolidFocusedColor;
    }

    public ShapeDrawableBuilder setSolidSelectedColor(Integer color) {
        mSolidSelectedColor = color;
        return this;
    }

    @Nullable
    public Integer getSolidSelectedColor() {
        return mSolidSelectedColor;
    }

    public ShapeDrawableBuilder setRadius(float radius) {
        return setRadius(radius, radius, radius, radius);
    }

    public ShapeDrawableBuilder setRadius(float topLeftRadius, float topRightRadius, float bottomLeftRadius, float bottomRightRadius) {
        mTopLeftRadius = topLeftRadius;
        mTopRightRadius = topRightRadius;
        mBottomLeftRadius = bottomLeftRadius;
        mBottomRightRadius = bottomRightRadius;
        return this;
    }

    public float getTopLeftRadius() {
        return mTopLeftRadius;
    }

    public float getTopRightRadius() {
        return mTopRightRadius;
    }

    public float getBottomLeftRadius() {
        return mBottomLeftRadius;
    }

    public float getBottomRightRadius() {
        return mBottomRightRadius;
    }

    public ShapeDrawableBuilder setGradientColors(int startColor, int endColor) {
        return setGradientColors(new int[]{startColor, endColor});
    }

    public ShapeDrawableBuilder setGradientColors(int startColor, int centerColor, int endColor) {
        return setGradientColors(new int[]{startColor, centerColor, endColor});
    }

    public ShapeDrawableBuilder setGradientColors(int[] colors) {
        mGradientColors = colors;
        return this;
    }

    @Nullable
    public int[] getGradientColors() {
        return mGradientColors;
    }

    public boolean isGradientColors() {
        return mGradientColors != null &&
                mGradientColors.length > 0;
    }

    public void clearGradientColors() {
        mGradientColors = null;
    }

    public ShapeDrawableBuilder setUseLevel(boolean useLevel) {
        mUseLevel = useLevel;
        return this;
    }

    public boolean isUseLevel() {
        return mUseLevel;
    }

    public ShapeDrawableBuilder setAngle(int angle) {
        mAngle = angle;
        return this;
    }

    public int getAngle() {
        return mAngle;
    }

    public ShapeDrawableBuilder setGradientType(int type) {
        mGradientType = type;
        return this;
    }

    public int getGradientType() {
        return mGradientType;
    }

    public ShapeDrawableBuilder setCenterX(float x) {
        mCenterX = x;
        return this;
    }

    public float getCenterX() {
        return mCenterX;
    }

    public ShapeDrawableBuilder setCenterY(float y) {
        mCenterY = y;
        return this;
    }

    public float getCenterY() {
        return mCenterY;
    }

    public ShapeDrawableBuilder setGradientRadius(int radius) {
        mGradientRadius = radius;
        return this;
    }

    public int getGradientRadius() {
        return mGradientRadius;
    }

    public ShapeDrawableBuilder setStrokeColor(int color) {
        mStrokeColor = color;
        return this;
    }

    public int getStrokeColor() {
        return mStrokeColor;
    }

    public ShapeDrawableBuilder setStrokePressedColor(Integer color) {
        mStrokePressedColor = color;
        return this;
    }

    @Nullable
    public Integer getStrokePressedColor() {
        return mStrokePressedColor;
    }

    public ShapeDrawableBuilder setStrokeCheckedColor(Integer color) {
        mStrokeCheckedColor = color;
        return this;
    }

    @Nullable
    public Integer getStrokeCheckedColor() {
        return mStrokeCheckedColor;
    }

    public ShapeDrawableBuilder setStrokeDisabledColor(Integer color) {
        mStrokeDisabledColor = color;
        return this;
    }

    @Nullable
    public Integer getStrokeDisabledColor() {
        return mStrokeDisabledColor;
    }

    public ShapeDrawableBuilder setStrokeFocusedColor(Integer color) {
        mStrokeFocusedColor = color;
        return this;
    }

    @Nullable
    public Integer getStrokeFocusedColor() {
        return mStrokeFocusedColor;
    }

    public ShapeDrawableBuilder setStrokeSelectedColor(Integer color) {
        mStrokeSelectedColor = color;
        return this;
    }

    @Nullable
    public Integer getStrokeSelectedColor() {
        return mStrokeSelectedColor;
    }

    public ShapeDrawableBuilder setStrokeWidth(int width) {
        mStrokeWidth = width;
        return this;
    }

    public int getStrokeWidth() {
        return mStrokeWidth;
    }

    public ShapeDrawableBuilder setDashWidth(int width) {
        mDashWidth = width;
        return this;
    }

    public int getDashWidth() {
        return mDashWidth;
    }

    public ShapeDrawableBuilder setDashGap(int gap) {
        mDashGap = gap;
        return this;
    }

    public int getDashGap() {
        return mDashGap;
    }

    public boolean isDashLineEnable() {
        return mDashGap > 0;
    }

    public Drawable buildBackgroundDrawable() {
        if (!isGradientColors() && mSolidColor == NO_COLOR && mStrokeColor == NO_COLOR) {
            return null;
        }

        GradientDrawable defaultDrawable = createGradientDrawable(mSolidColor, mStrokeColor);
        // 判断是否设置了渐变色
        if (isGradientColors()) {
            defaultDrawable.setColors(mGradientColors);
        }

        if (mSolidPressedColor != null && mStrokePressedColor != null &&
                mSolidCheckedColor != null && mStrokeCheckedColor != null &&
                mSolidDisabledColor != null && mStrokeDisabledColor != null &&
                mSolidFocusedColor != null && mStrokeFocusedColor != null &&
                mSolidSelectedColor != null && mStrokeSelectedColor != null) {
            return defaultDrawable;
        }

        StateListDrawable drawable = new StateListDrawable();
        if (mSolidPressedColor != null || mStrokePressedColor != null) {
            drawable.addState(new int[]{android.R.attr.state_pressed}, createGradientDrawable(
                    mSolidPressedColor != null ? mSolidPressedColor : mSolidColor,
                    mStrokePressedColor != null ? mStrokePressedColor : mStrokeColor));
        }
        if (mSolidCheckedColor != null || mStrokeCheckedColor != null) {
            drawable.addState(new int[]{android.R.attr.state_checked}, createGradientDrawable(
                    mSolidCheckedColor != null ? mSolidCheckedColor : mSolidColor,
                    mStrokeCheckedColor != null ? mStrokeCheckedColor : mStrokeColor));
        }
        if (mSolidDisabledColor != null || mStrokeDisabledColor != null) {
            drawable.addState(new int[]{-android.R.attr.state_enabled}, createGradientDrawable(
                    mSolidDisabledColor != null ? mSolidDisabledColor : mSolidColor,
                    mStrokeDisabledColor != null ? mStrokeDisabledColor : mStrokeColor));
        }
        if (mSolidFocusedColor != null || mStrokeFocusedColor != null) {
            drawable.addState(new int[]{android.R.attr.state_focused}, createGradientDrawable(
                    mSolidFocusedColor != null ? mSolidFocusedColor : mSolidColor,
                    mStrokeFocusedColor != null ? mStrokeFocusedColor : mStrokeColor));
        }
        if (mSolidSelectedColor != null || mStrokeSelectedColor != null) {
            drawable.addState(new int[]{android.R.attr.state_selected}, createGradientDrawable(
                    mSolidSelectedColor != null ? mSolidSelectedColor : mSolidColor,
                    mStrokeSelectedColor != null ? mStrokeSelectedColor : mStrokeColor));
        }

        drawable.addState(new int[]{}, defaultDrawable);
        return drawable;
    }

    public void intoBackground() {
        Drawable drawable = buildBackgroundDrawable();
        if (drawable == null) {
            return;
        }
//        if (isDashLineEnable() || isShadowEnable()) {
//            // 需要关闭硬件加速,否则虚线或者阴影在某些手机上面无法生效
//            mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//        }
        mView.setBackground(drawable);
    }

    private GradientDrawable createGradientDrawable(int solidColor, int strokeColor) {
        //top-left, top-right, bottom-right, bottom-left.
        float[] radius = {mTopLeftRadius, mTopLeftRadius, mTopRightRadius, mTopRightRadius, mBottomRightRadius, mBottomRightRadius, mBottomLeftRadius, mBottomLeftRadius};
        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setShape(mShape);                                              // 形状
        gradientDrawable.setSize(mShapeWidth, mShapeHeight);                            // 尺寸
        gradientDrawable.setCornerRadii(radius);                                        // 圆角
        gradientDrawable.setColor(solidColor);                                          // 颜色
        gradientDrawable.setUseLevel(mUseLevel);
        gradientDrawable.setStroke(strokeColor, mStrokeWidth, mDashWidth, mDashGap);    // 边框

        gradientDrawable.setOrientation(toOrientation(mAngle));
        gradientDrawable.setGradientType(mGradientType);
        gradientDrawable.setGradientRadius(mGradientRadius);
        gradientDrawable.setGradientCenter(mCenterX, mCenterY);
        return gradientDrawable;
    }

    public GradientDrawable.Orientation toOrientation(int angle) {
        angle %= 360;
        // angle 必须为 45 的整数倍
        if (angle % 45 == 0) {
            switch (angle) {
                case 0:
                    return GradientDrawable.Orientation.LEFT_RIGHT;
                case 45:
                    return GradientDrawable.Orientation.BL_TR;
                case 90:
                    return GradientDrawable.Orientation.BOTTOM_TOP;
                case 135:
                    return GradientDrawable.Orientation.BR_TL;
                case 180:
                    return GradientDrawable.Orientation.RIGHT_LEFT;
                case 225:
                    return GradientDrawable.Orientation.TR_BL;
                case 270:
                    return GradientDrawable.Orientation.TOP_BOTTOM;
                case 315:
                    return GradientDrawable.Orientation.TL_BR;
                default:
                    break;
            }
        }
        return GradientDrawable.Orientation.LEFT_RIGHT;
    }
}

设置shape背景场景,可以 一 一对应属性

Android进阶之路 - TextView文本渐变_第9张图片

场景判断

Android进阶之路 - TextView文本渐变_第10张图片

背景属性具体设置方式,包含角度处理

Android进阶之路 - TextView文本渐变_第11张图片

TextColorBuilder

主要作用于 TextView本身属性设置

package com.example.shapefontbg.shape.builder;

import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.text.SpannableStringBuilder;
import android.widget.TextView;

import androidx.annotation.Nullable;

import com.example.shapefontbg.shape.LinearGradientFontSpan;
import com.example.shapefontbg.shape.styleable.ITextColorStyleable;


/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/28
 * desc   : TextColor 构建类
 */
@SuppressWarnings("unused")
public final class TextColorBuilder {

    private final TextView mTextView;

    private int mTextColor;
    private Integer mTextPressedColor;
    private Integer mTextCheckedColor;
    private Integer mTextDisabledColor;
    private Integer mTextFocusedColor;
    private Integer mTextSelectedColor;

    private int[] mTextGradientColors;
    private int mTextGradientOrientation;

    public TextColorBuilder(TextView textView, TypedArray typedArray, ITextColorStyleable styleable) {
        mTextView = textView;
        mTextColor = typedArray.getColor(styleable.getTextColorStyleable(), textView.getTextColors().getDefaultColor());
        if (typedArray.hasValue(styleable.getTextPressedColorStyleable())) {
            mTextPressedColor = typedArray.getColor(styleable.getTextPressedColorStyleable(), mTextColor);
        }
        if (styleable.getTextCheckedColorStyleable() > 0 && typedArray.hasValue(styleable.getTextCheckedColorStyleable())) {
            mTextCheckedColor = typedArray.getColor(styleable.getTextCheckedColorStyleable(), mTextColor);
        }
        if (typedArray.hasValue(styleable.getTextDisabledColorStyleable())) {
            mTextDisabledColor = typedArray.getColor(styleable.getTextDisabledColorStyleable(), mTextColor);
        }
        if (typedArray.hasValue(styleable.getTextFocusedColorStyleable())) {
            mTextFocusedColor = typedArray.getColor(styleable.getTextFocusedColorStyleable(), mTextColor);
        }
        if (typedArray.hasValue(styleable.getTextSelectedColorStyleable())) {
            mTextSelectedColor = typedArray.getColor(styleable.getTextSelectedColorStyleable(), mTextColor);
        }

        if (typedArray.hasValue(styleable.getTextStartColorStyleable()) && typedArray.hasValue(styleable.getTextEndColorStyleable())) {
            if (typedArray.hasValue(styleable.getTextCenterColorStyleable())) {
                mTextGradientColors = new int[]{typedArray.getColor(styleable.getTextStartColorStyleable(), mTextColor),
                        typedArray.getColor(styleable.getTextCenterColorStyleable(), mTextColor),
                        typedArray.getColor(styleable.getTextEndColorStyleable(), mTextColor)};
            } else {
                mTextGradientColors = new int[]{typedArray.getColor(styleable.getTextStartColorStyleable(), mTextColor),
                        typedArray.getColor(styleable.getTextEndColorStyleable(), mTextColor)};
            }
        }

        mTextGradientOrientation = typedArray.getColor(styleable.getTextGradientOrientationStyleable(),
                LinearGradientFontSpan.GRADIENT_ORIENTATION_HORIZONTAL);
    }

    public TextColorBuilder setTextColor(int color) {
        mTextColor = color;
        clearTextGradientColors();
        return this;
    }

    public int getTextColor() {
        return mTextColor;
    }

    public TextColorBuilder setTextPressedColor(Integer color) {
        mTextPressedColor = color;
        return this;
    }

    @Nullable
    public Integer getTextPressedColor() {
        return mTextPressedColor;
    }

    public TextColorBuilder setTextCheckedColor(Integer color) {
        mTextCheckedColor = color;
        return this;
    }

    @Nullable
    public Integer getTextCheckedColor() {
        return mTextCheckedColor;
    }

    public TextColorBuilder setTextDisabledColor(Integer color) {
        mTextDisabledColor = color;
        return this;
    }

    @Nullable
    public Integer getTextDisabledColor() {
        return mTextDisabledColor;
    }

    public TextColorBuilder setTextFocusedColor(Integer color) {
        mTextFocusedColor = color;
        return this;
    }

    @Nullable
    public Integer getTextFocusedColor() {
        return mTextFocusedColor;
    }

    public TextColorBuilder setTextSelectedColor(Integer color) {
        mTextSelectedColor = color;
        return this;
    }

    @Nullable
    public Integer getTextSelectedColor() {
        return mTextSelectedColor;
    }

    public TextColorBuilder setTextGradientColors(int startColor, int endColor) {
        return setTextGradientColors(new int[]{startColor, endColor});
    }

    public TextColorBuilder setTextGradientColors(int startColor, int centerColor, int endColor) {
        return setTextGradientColors(new int[]{startColor, centerColor, endColor});
    }

    public TextColorBuilder setTextGradientColors(int[] colors) {
        mTextGradientColors = colors;
        return this;
    }

    @Nullable
    public int[] getTextGradientColors() {
        return mTextGradientColors;
    }

    public boolean isTextGradientColors() {
        return mTextGradientColors != null && mTextGradientColors.length > 0;
    }

    public void clearTextGradientColors() {
        mTextGradientColors = null;
    }

    public TextColorBuilder setTextGradientOrientation(int orientation) {
        mTextGradientOrientation = orientation;
        return this;
    }

    public int getTextGradientOrientation() {
        return mTextGradientOrientation;
    }

    public SpannableStringBuilder buildLinearGradientSpannable(CharSequence text) {
        return LinearGradientFontSpan.buildLinearGradientSpannable(text, mTextGradientColors, null, mTextGradientOrientation);
    }

    public ColorStateList buildColorState() {
        if (mTextPressedColor == null &&
                mTextCheckedColor == null &&
                mTextDisabledColor == null &&
                mTextFocusedColor == null &&
                mTextSelectedColor == null) {
            return ColorStateList.valueOf(mTextColor);
        }

        int maxSize = 6;
        int arraySize = 0;
        int[][] statesTemp = new int[maxSize][];
        int[] colorsTemp = new int[maxSize];

        if (mTextPressedColor != null) {
            statesTemp[arraySize] = new int[]{android.R.attr.state_pressed};
            colorsTemp[arraySize] = mTextPressedColor;
            arraySize++;
        }
        if (mTextCheckedColor != null) {
            statesTemp[arraySize] = new int[]{android.R.attr.state_checked};
            colorsTemp[arraySize] = mTextCheckedColor;
            arraySize++;
        }
        if (mTextDisabledColor != null) {
            statesTemp[arraySize] = new int[]{-android.R.attr.state_enabled};
            colorsTemp[arraySize] = mTextDisabledColor;
            arraySize++;
        }
        if (mTextFocusedColor != null) {
            statesTemp[arraySize] = new int[]{android.R.attr.state_focused};
            colorsTemp[arraySize] = mTextFocusedColor;
            arraySize++;
        }
        if (mTextSelectedColor != null) {
            statesTemp[arraySize] = new int[]{android.R.attr.state_selected};
            colorsTemp[arraySize] = mTextSelectedColor;
            arraySize++;
        }

        statesTemp[arraySize] = new int[]{};
        colorsTemp[arraySize] = mTextColor;
        arraySize++;

        int[][] states;
        int[] colors;
        if (arraySize == maxSize) {
            states = statesTemp;
            colors = colorsTemp;
        } else {
            states = new int[arraySize][];
            colors = new int[arraySize];
            // 对数组进行拷贝
            System.arraycopy(statesTemp, 0, states, 0, arraySize);
            System.arraycopy(colorsTemp, 0, colors, 0, arraySize);
        }
        return new ColorStateList(states, colors);
    }

    public void intoTextColor() {
        if (isTextGradientColors()) {
            mTextView.setText(buildLinearGradientSpannable(mTextView.getText()));
            return;
        }
        mTextView.setTextColor(buildColorState());
    }
}

LinearGradientFontSpan 文本渐变核心类

通过 LinearGradient 设置渐变效果

package com.example.shapefontbg.shape;

import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ReplacementSpan;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;

/**
 * author : Android 轮子哥
 * github : https://github.com/getActivity/ShapeView
 * time   : 2021/08/17
 * desc   : 支持直接定义文本渐变色的 Span
 */
public class LinearGradientFontSpan extends ReplacementSpan {

    /**
     * 水平渐变方向
     */
    public static final int GRADIENT_ORIENTATION_HORIZONTAL = LinearLayout.HORIZONTAL;
    /**
     * 垂直渐变方向
     */
    public static final int GRADIENT_ORIENTATION_VERTICAL = LinearLayout.VERTICAL;

    /**
     * 构建一个文字渐变色的 Spannable 对象
     */
    public static SpannableStringBuilder buildLinearGradientSpannable(CharSequence text, int[] colors, float[] positions, int orientation) {
        SpannableStringBuilder builder = new SpannableStringBuilder(text);
        //下面声明了建造方法,所以支持链式设置
        LinearGradientFontSpan span = new LinearGradientFontSpan()
                .setTextGradientColor(colors)
                .setTextGradientOrientation(orientation)
                .setTextGradientPositions(positions);
        builder.setSpan(span, 0, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        return builder;
    }

    /**
     * 测量的文本宽度
     */
    private float mMeasureTextWidth;

    /**
     * 文字渐变方向
     */
    private int mTextGradientOrientation;
    /**
     * 文字渐变颜色组
     */
    private int[] mTextGradientColor;
    /**
     * 文字渐变位置组
     */
    private float[] mTextGradientPositions;

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetricsInt) {
        mMeasureTextWidth = paint.measureText(text, start, end);

        // 这段不可以去掉,字体高度没设置,会出现 draw 方法没有被调用的问题
        // 详情请见:https://stackoverflow.com/questions/20069537/replacementspans-draw-method-isnt-called
        Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
        if (fontMetricsInt != null) {
            fontMetricsInt.top = metrics.top;
            fontMetricsInt.ascent = metrics.ascent;
            fontMetricsInt.descent = metrics.descent;
            fontMetricsInt.bottom = metrics.bottom;
        }
        return (int) mMeasureTextWidth;
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
        LinearGradient linearGradient;
        if (mTextGradientOrientation == GRADIENT_ORIENTATION_VERTICAL) {
            linearGradient = new LinearGradient(0, 0, 0, paint.descent() - paint.ascent(),
                    mTextGradientColor, mTextGradientPositions, Shader.TileMode.REPEAT);
        } else {
            linearGradient = new LinearGradient(x, 0, x + mMeasureTextWidth, 0,
                    mTextGradientColor, mTextGradientPositions, Shader.TileMode.REPEAT);
        }
        paint.setShader(linearGradient);

        int alpha = paint.getAlpha();
        // 判断是否给画笔设置了透明度
        if (alpha != 255) {
            // 如果是则设置不透明
            paint.setAlpha(255);
        }
        canvas.drawText(text, start, end, x, y, paint);
        // 绘制完成之后将画笔的透明度还原回去
        paint.setAlpha(alpha);
    }

    public LinearGradientFontSpan setTextGradientOrientation(int orientation) {
        mTextGradientOrientation = orientation;
        return this;
    }

    public LinearGradientFontSpan setTextGradientColor(int[] colors) {
        mTextGradientColor = colors;
        return this;
    }

    public LinearGradientFontSpan setTextGradientPositions(float[] positions) {
        mTextGradientPositions = positions;
        return this;
    }
}

测量渐变文本的宽度

Android进阶之路 - TextView文本渐变_第12张图片

渐变方向、渐变颜色、画笔透明度处理等~

Android进阶之路 - TextView文本渐变_第13张图片

你可能感兴趣的:(Android进阶之路,Android,渐变效果,文本渐变,TextView渐变)