Android进阶之路 - TextView文本渐变

news2024/12/26 16:41:34

那天做需求的时候,遇到一个小功能,建立在前人栽树,后人乘凉的情况下,仅用片刻就写完了;说来惭愧,我以前并未写过文本渐变的需求,脑中也仅有一个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;
    }
}

测量渐变文本的宽度

在这里插入图片描述

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Proteus的网络标号与总线

Proteus为了减少过多、复杂的连线&#xff0c;可以使用网络标号与总线配合使用。 Proteus的导线上添加了网络标号&#xff0c;意味着在Proteus上相同的网络标号是连在一起的&#xff0c;所说在图纸上看不出来。 如下图是比较好的Proteus中使用总线的绘制的图纸。可以效仿着画…

【坤坤之夜 KUNKUNNIGHT】- 探索神秘世界,开启刺激冒险之旅!

你是否准备好迎接一个充满挑战和惊喜的单机游戏体验&#xff1f;坤坤之夜&#xff08;KUNKUNNIGHT&#xff09;将带你进入一个神秘而刺激的世界&#xff0c;让你尽情探索&#xff0c;解锁各种有趣的技能和道具&#xff0c;解决谜题&#xff0c;完成各种挑战。 坤坤之夜的游戏画…

CodeMeter软件保护及授权管理解决方案(二)

客户端管理工具 CodeMeter Runtime是CodeMeter解决方案中的重要组成部分&#xff0c;其为独立软件包&#xff0c;开发者需要把CodeMeter Runtime和加密后的软件一起发布。CodeMeter Runtim包括以下组件用于实现授权的使用&#xff1a; CodeMeter License Server授权服务器 Co…

我叫:基数排序【JAVA】

1.自我介绍 基数排序(radix sort)属于“分配式排序” (distribution sort)&#xff0c;又称“桶子法” (bucket sort)或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,是‘桶排序’的扩展 2.基本思想 将所有待比较数值统一为同样的数位长度,数位较短的数…

【图论】重庆大学图论与应用课程期末复习资料(私人复习资料)

考试章节范围 第一章&#xff1a;1.1、1.2、1.3 填空 顶点集和边集都有限的图&#xff0c;称为有限图只有一个顶点的图&#xff0c;称为平凡图边集为空的图&#xff0c;称为空图顶点数为n的图&#xff0c;称为n阶图连接两个相同顶点的边的条数称为边的重数&#xff1b;重数大…

荣耀冲击高端,一边推新「修路」,一边降价「拆桥」

作者 | 辰纹 来源 | 洞见新研社 从2020年11月17日与华为分家&#xff0c;开启独立创业之路&#xff0c;到成功逆袭&#xff0c;今年第三季度以18%的份额重回中国智能手机市场榜首&#xff0c;荣耀用了3年时间。 图源&#xff1a;Canalys 在这三年时间内&#xff0c;荣耀经历…

unity学习笔记07

一、组件 有几个物体他们之间有着重复的功能&#xff0c;该如何避免重复的去写代码&#xff1f; 可以将一些相同的功能写成一个组件&#xff0c;也就是组件就等同于功能。 什么是组件&#xff1f; 在Unity中&#xff0c;游戏物体是不具备任何功能的&#xff0c;如果想要为其…

解决api-ms-win-crt-runtime-l1-1-0.dll丢失的问题,全是干货分享

今天我的电脑中突然出现关于“api-ms-win-crt-runtime-l1-1-0.dll”的错误提示&#xff0c;关闭提示后再次打开程序依然不能正常打开&#xff0c;出现这样的问题突然不知道是因为什么&#xff0c;于是就去了解了关于出现api-ms-win-crt-runtime-l1-1-0.dll错误的问题&#xff0…

一款LED段码显示屏驱动芯片方案

一、基本概述 TM1620是一种LED&#xff08;发光二极管显示器&#xff09;驱动控制专用IC,内部集成有MCU数字接口、数据锁存器、LED驱动等电路。本产品质量可靠、稳定性好、抗干扰能力强。 二、基本特性 采用CMOS工艺 显示模式&#xff08;8段6位&#xff5e;10段4位&#xff…

YOLOv5原创改进:全维动态卷积再改进,GCODConv

目录 一、原理 网络结构 二、代码 三、应用到YOLOv5中 一、原理

自定义注解的定义及使用场景

文章目录 1. 自定义注解如何使用2. 自定义注解使用场景2.1 自定义注解使用AOP做权限校验2.2 自定义注解使用AOP记录用户操作日志2.3 自定义注解使用AOP记录接口请求时长 1. 自定义注解如何使用 需要使用interface修饰&#xff0c;加上三个元注解 Documented&#xff1a;生成API…

微信预约小程序制作

对于许多新手来说&#xff0c;制作微信预约小程序可能是一项挑战&#xff0c;但并非不可能。本文将通过详细的步骤&#xff0c;指导您从零开始制作一个微信预约小程序。首先&#xff0c;您需要找一个合适的第三方制作平台或工具&#xff0c;乔拓云网就是其中之一。 找一个合适的…

入门指南:Vue的安装配置和开发环境设置

背景&#xff1a; ​ 这里想讲一讲为什么使用框架&#xff0c;而不使用原生的HTML、CSS、JavaScript写。原生开发虽然灵活&#xff0c;但在大型项目中可能导致代码重复、维护困难等问题&#xff0c;不符合软件工程的"高内聚低耦合"原则。例如&#xff0c;如果每个页…

人工智能 - 图像分类:发展历史、技术全解与实战

目录 一、&#xff1a;图像分类的历史与进展历史回顾深度学习的革命当前趋势未来展望 二&#xff1a;核心技术解析图像预处理神经网络基础卷积神经网络&#xff08;CNN&#xff09;深度学习框架 第三部分&#xff1a;核心代码与实现环境搭建数据加载和预处理构建CNN模型模型训练…

Python异常处理:try语句的应用与技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 异常处理在Python中是至关重要的。try-except是用于捕获和处理异常的核心机制之一。让我们深入了解如何使用try-except&#xff0c;处理各种异常情况。 try-except语句 在编程中&#xff0c;异常是指运行时发生…

linux环境下编译安装OpenCV For Java(CentOS 7)

最近在业余时间学习了一些有关图像处理的代码&#xff0c;但是只能本地处理&#xff0c;满足不了将来开放远程服务的需求。 因此&#xff0c;查找并参考了一些资料&#xff0c;成功在centos7环境安装上了opencv 460。 下面上具体安装步骤&#xff0c;希望能帮到有需要的同学。 …

贝叶斯Python实践

贝叶斯统计学是一种基于贝叶斯定理的概率推理方法&#xff0c;它在机器学习领域得到了广泛的应用。Python作为一门简洁、灵活和易学的编程语言&#xff0c;为贝叶斯统计学的实践提供了强大的工具和库。在本文中&#xff0c;我们将探讨贝叶斯在Python中的实践&#xff0c;包括贝…

数据集笔记 :PEMS-BAY

数据地址&#xff1a;DCRNN - Google 云端硬盘 各station 位置&#xff1a;DCRNN/data/sensor_graph/graph_sensor_locations_bay.csv at master liyaguang/DCRNN (github.com) 1 读取 数据 import h5py fileDownloads/pems-bay.h5fh5py.File(file,r) f.keys()f[speed] #&…

氪了几百亿,字节游戏停止了“跳动”

目录 一、氪了几百亿&#xff0c;字节游戏停止了“跳动” 二微软推出跨平台框架 ML.NET 3.0 版&#xff1a;强化深度学习、加强AI效率 一、氪了几百亿&#xff0c;字节游戏停止了“跳动” 朝夕光年&#xff0c;扑了 11月26日&#xff0c;脉脉社区的一个截图内容引起大众热议…

简单位运算

文章目录 求 n n n 的第 k k k 位是二进制的几lowbit(n)操作求解 n n n 的最后一个 1 1 1题目练习AcWing 801. 二进制中1的个数CODE1 原码、补码、反码 求 n n n 的第 k k k 位是二进制的几 我们需要用到&运算符&#xff1a;两位都为 1 1 1 时结果才为 1 1 1 &…