小工具知识网

当前位置: 首页 > 行业知识 > Android进阶之路 - TextView文本渐变

行业知识

Android进阶之路 - TextView文本渐变

2026-11-05 19:51:09 来源:互联网转载

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

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

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

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

                效果

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

                需求效果

                渐变背景

                渐变文本

                实现效果

                本文只针对item样式中的 右上角的双重渐变(渐变背景、渐变文本)实现


                基础思考

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

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

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

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

                  关于渐变位置主要有startColor、centerColor、endColor ,如根据设计图的话,可仅设置startColor、endColor

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

                              

                  开发实践

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

                  关于 ShapeView 的集成,可自行前往作者项目主页查看,此处仅做部分摘要

                  项目结构

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

                  使用方式

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

                      

                  集成学习

                  ShapeTextView 自定义控件

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

                  • 初始化背景属性、文本属性(内部)
                  • 通过读取typefaceScale属性,设置对应字体加粗效果(内部)
                  • 增添动态设置Text、setTextColor、TypefaceScale方法(支持外部调用)
                    package com.example.shapefontbg.shapeimport android.content.Contextimport android.graphics.Canvasimport android.graphics.Paintimport android.util.AttributeSetimport androidx.appcompat.widget.AppCompatTextViewimport com.example.shapefontbg.Rimport com.example.shapefontbg.shape.builder.ShapeDrawableBuilderimport com.example.shapefontbg.shape.builder.TextColorBuilderimport 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 自定义属性

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

                    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.styleableimport 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

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

                      • 单一职责,解耦(分别作用于背景和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;    }}

                        测量渐变文本的宽度

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

上一篇:微信审核与屏蔽应对秘籍,网站如何巧妙规避微信审核与屏蔽挑战?

下一篇:【腾讯云 TDSQL-C Serverless 产品体验】以TDSQL-C Mysql Serverless 作为数据中心爬取豆瓣图书数据