Android进阶之路 - TextView文本渐变

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

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

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

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

效果

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

需求效果

在这里插入图片描述

渐变背景

在这里插入图片描述

渐变文本

在这里插入图片描述

实现效果

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


基础思考

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

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

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

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<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 的集成,可自行前往作者项目主页查看,此处仅做部分摘要
在这里插入图片描述

项目结构

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

在这里插入图片描述

使用方式

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

<?xml version="1.0" encoding="utf-8"?>
<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()

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

在这里插入图片描述

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

在这里插入图片描述


shape_attr 自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Shape 形状(默认是矩形) -->
    <attr name="shape">
        <!-- 矩形 -->
        <enum name="rectangle" value="0" />
        <!-- 椭圆形 -->
        <enum name="oval" value="1" />
        <!-- 线条 -->
        <enum name="line" value="2" />
        <!-- 圆环 -->
        <enum name="ring" value="3" />
    </attr>
    <!-- Shape 宽度 -->
    <attr name="shape_width" format="dimension" />
    <!-- Shape 高度 -->
    <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" />
    <!-- 渐变角度(仅用于线性渐变。必须是 0-315 范围内的值,并且是 45 的倍数) -->
    <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>
    <!-- 渐变中心 X 点坐标的相对位置(默认值为 0.5)-->
    <attr name="shape_centerX" format="float|fraction" />
    <!-- 渐变中心 Y 点坐标的相对位置(默认值为 0.5)-->
    <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" />
    <!-- 边框虚线宽度(为 0 就是实线,大于 0 就是虚线) -->
    <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>

    <!-- CheckBox 或者 RadioButton 图标(默认状态) -->
    <attr name="shape_buttonDrawable" format="reference" />
    <!-- CheckBox 或者 RadioButton 图标(按下状态) -->
    <attr name="shape_buttonPressedDrawable" format="reference" />
    <!-- CheckBox 或者 RadioButton 图标(选中状态) -->
    <attr name="shape_buttonCheckedDrawable" format="reference" />
    <!-- CheckBox 或者 RadioButton 图标(禁用状态) -->
    <attr name="shape_buttonDisabledDrawable" format="reference" />
    <!-- CheckBox 或者 RadioButton 图标(焦点状态) -->
    <attr name="shape_buttonFocusedDrawable" format="reference" />
    <!-- CheckBox 或者 RadioButton 图标(选择状态) -->
    <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类有很多个,如果后续有扩展的话,也可以再用工厂模式或策略模式二次封装;

在这里插入图片描述

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即可

在这里插入图片描述


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背景场景,可以 一 一对应属性

在这里插入图片描述

场景判断

在这里插入图片描述

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

在这里插入图片描述

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;
    }
}

测量渐变文本的宽度

在这里插入图片描述

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/201681.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用代码评论代替代码注释

在一个软件项目中&#xff0c;某些逻辑部分可能非常复杂&#xff0c;容易让人困惑。为了确保其他开发人员能够理解这些代码&#xff0c;同时也为了自己回顾时能够快速上手&#xff0c;我们通常会编写相关文档或添加大量注释来对这些复杂的逻辑进行解释。这样做的好处是能够提高…

Go语言基础:包、函数、语句和注释解析

一个 Go 文件包含以下几个部分&#xff1a; 包声明导入包函数语句和表达式 看下面的代码&#xff0c;更好地理解它&#xff1a; 例子 package mainimport "fmt"func main() { fmt.Println("Hello World!") }例子解释 第 1 行&#xff1a; 在 Go 中&am…

go学习之文件操作与命令行参数

文章目录 一、文件操作1.基本介绍2.常用文件操作函数和方法3.关于文件操作应用实例4.写文件操作应用实例&#xff08;创建文件并写入文件&#xff09;1&#xff09;基本介绍2&#xff09;基本应用实例-方式一 5.判断文件是否存在6.统计英文、数字、空格和其他字符数量 二、命令…

Kubernetes

Kubernetes Docker的安装Docker安装&#xff1a;安装docker依赖环境配置国内docker-ce的yum源&#xff08;这里采用的是阿里云&#xff09;安装docker。插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自…

Sui主网升级至V1.14.2版本

Sui主网现已升级至V1.14.2版本&#xff0c;同时Sui协议升级至31版本。其他升级要点如下所示&#xff1a; #14875: [修复] 为所有权限设置共识度量值。 #14811: [Narwhal] 改进每个权限的共识信息度量的可用性。 完整变更日志&#xff1a;Release mainnet-v1.14.2 MystenL…

考虑极端天气线路脆弱性的配电网分布式电源配置优化模型_IEEE33节点(附带Matlab代码)

随着新能源技术及智能电网的发展&#xff0c;越来越多的分布式电源加入配电网中&#xff0c;不仅改变了配电网结构及供电方式&#xff0c;而且提升了配电网的供电质量。但是在全球气候变暖的背景下&#xff0c;极端天气发生的频率也越来越高&#xff0c;一旦发生必将对配电网系…

HashMap的死循环及数据覆盖问题

目录 一&#xff0c;HashMap 线程不安全的原因 二&#xff0c;HashMap 死循环问题 死循环发生的条件 死循环的具体过程 死循环执行步骤1 死循环执行步骤2 死循环执行步骤3 三&#xff0c;HashMap 数据覆盖问题 数据覆盖执行流程1 数据覆盖执行流程2 数据覆盖执行流…

8、CobaltStrike使用

文章目录 一、实验拓扑图二、实验步骤 一、实验拓扑图 二、实验步骤 1、登录"Kali"操作机&#xff0c;在终端中进入CS文件夹&#xff0c;然后使用命令chmod x teamserver给teamserver文件赋予执行权限&#xff0c;然后查看当前主机的本地IP地址。 2、启动服务端服务…

SAS9.2软件“OLE:对象的类没有在注册数据库中注册“问题的解决. 2023-11-25

操作系统测试平台: Win7 sp1 32bit (6.1.7601.26321 (Win7 RTM)) ; Win 11 64bit(具体版本不详) 其它win平台理论上也可以,可自行测试 1.安装依赖库(必要步骤) 下载地址: Microsoft Visual C 2005 Redistributable 下载 Microsoft Visual C 2008 Redistributable 官方vc库总…

信号类型(通信)——最小频移键控(MSK)

系列文章目录 《信号类型&#xff08;通信&#xff09;——仿真》 《信号类型&#xff08;通信&#xff09;——QAM调制信号》 《信号类型&#xff08;通信&#xff09;——QPSK、OQPSK、IJF_OQPSK调制信号》 目录 前言 一、MSK信号特点 1.1、最小频移 1.2、相位连续 二…

【Vulnhub 靶场】【Coffee Addicts: 1】【简单-中等】【20210520】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/coffee-addicts-1,699/ 靶场下载&#xff1a;https://download.vulnhub.com/coffeeaddicts/coffeeaddicts.ova 靶场难度&#xff1a;简单 - 中等 发布日期&#xff1a;2021年5月20日 文件大小&#xff1a;1.3 …

Spring——感谢尚硅谷官方文档

Spring——尚硅谷学习笔记 1 Spring简介&#x1f47e;1.1 Spring概述1.2 Spring Framework1.2.1 Spring Framework特性1.2.2 Spring Framework五大功能模块 2 IOC-IOC容器思想&#x1f47e;IOC容器思想IOC在Spring中的实现 3 IOC-基于XML文件管理Bean&#x1f47e;3.1 准备工作…

拼多多商品详情API接口,详情页接口,宝贝详情页接口,商品属性接口,商品信息查询,商品详细信息接口,获取拼多多已拼数量,实时准确数据调用案例

接入拼多多API接口的操作流程一般包括以下步骤&#xff1a; 了解API接口&#xff1a;首先&#xff0c;你需要了解你要接入的API接口的文档和规范。这些信息通常可以在API提供商的官方文档或开发者门户网站上找到。文档通常会包含API的请求和响应格式、参数、权限等信息。获取A…

【LeetCode刷题】--77.组合

77.组合 class Solution {public List<List<Integer>> combine(int n, int k) {List<List<Integer>> ans new ArrayList<>();if( k < 0 || n < k){return ans;}Deque<Integer> list new ArrayDeque<>();dfs(ans,list,n,k,1)…

C语言——一个数如果恰好等于它的因子之和,这个数就称为“完全数”。

一个数如果恰好等于它的因子之和,这个数就称为“完全数”。例如,6的因子是 1、2、3,而6123。因此6是一个完全数。编程找出 1000 之内的所有完全数。 #include <stdio.h> int main() {int i, j, sum;for (i 1; i < 1000; i) {sum 0; //这一步很重要&#xff0c;每…

图片伪装,将RAR文件隐藏到图片里

下载链接 效果图&#xff1a; 代码&#xff1a; ECHO OFF TITLE PtoR MODE con COLS55 LINES25 color 0A:main cls echo.当前时间&#xff1a;%date% %time% echo.欢迎使用图片伪装&#xff0c;本脚本可以将RAR文件隐藏到图片里. echo.set /p "imagefile①请拖入图像文件…

hugegraph-server安装部署(docker)

1、安装docker不说了&#xff0c;可以直接看我文章一键安装docker https://blog.csdn.net/qq_41060647/article/details/131568289?spm1001.2014.3001.5502 2、一个docker-compose文件解决。 如果不使用mysql&#xff0c;可以将docker-compose.yml文件中的mysql配置修改为其他…

基于BP神经网络的手写体数字识别matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 filename dir(images\*.bmp); %图像文件格式 load BP.matfilename dir(test\*.bmp); …

Ubuntu16.04.4系统本地提权实验

目录 1.介绍&#xff1a; 2.实验&#xff1a; 3.总结&#xff1a; 1.介绍&#xff1a; 1.1&#xff1a;eBPF简介&#xff1a;eBPF(extendedBerkeleyPacketFilter)是内核源自于BPF的一套包过滤机制&#xff0c;BPF可以理解成用户与内核之间的一条通道&#xff0c;有非常强大的…

token认证机制,基于JWT的Token认证机制实现,安全性的问题

文章目录 token认证机制几种常用的认证机制HTTP Basic AuthOAuthCookie AuthToken AuthToken Auth的优点 基于JWT的Token认证机制实现JWT的组成认证过程登录请求认证 对Token认证的五点认识JWT的JAVA实现 基于JWT的Token认证的安全问题确保验证过程的安全性如何防范XSS Attacks…