前段时间的需求,涉及到大量带各种效果的字体的使用,比如描边、渐变、阴影等。一般情况下,我们在Android开发中用到花里胡哨字体的情况不多。但是,拿到了这样的需求,我们还是要实现这样一款支持多种效果的字体。其实,网上也有一些实现各种效果字体的方法。这篇博客,将把描边,渐变,阴影等结合到一起,实现一款自定义的文本框。

一、撸代码前的思考

        在开始动手之前,我们先明确一下需求。需求就是:一款支持描边、渐变、阴影效果的字体。为了实现上述的需求,我们需要做下面的工作:

1、文本框具有描边的属性

(1)是否描边:isStroke

(2)描边的颜色:strokeColor

(3)描边的宽度:strokeWidth

2、文本框具有颜色渐变的属性

(1)是否渐变:isGradient

(2)渐变是否是竖向的:isVertical

(3)渐变的起始颜色:startColor

(4)渐变的终止颜色:endColor

3、文本框具有阴影的属性

(1)是否显示阴影:isShadow

(2)阴影的颜色:shadowColor

(3)x方向的阴影:shadowX

(4)y方向的阴影:shadowY

(5)阴影的半径:shadowRadius

二、开始撸代码

1、在values文件夹下创建attrs.xml文件,把我们需求里的属性都定义好:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GradientTextView">
        <attr name="startColor" format="color" />
        <attr name="endColor" format="color" />
        <attr name="strokeColor" format="color"/>
        <attr name="strokeWidth" format="dimension"/>
        <attr name="isStroke" format="boolean" />
        <attr name="isGradient" format="boolean" />
        <attr name="isVertical" format="boolean" />
        <attr name="isShadow" format="boolean"/>
        <attr name="shadowX" format="integer"/>
        <attr name="shadowY" format="integer"/>
        <attr name="shadowRadius" format="integer"/>
        <attr name="shadowColor" format="color"/>
    </declare-styleable>
</resources>

2、初始化属性:

private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GradientTextView);
        startColor = ta.getColor(R.styleable.GradientTextView_startColor, 0xfffff8);
        endColor = ta.getColor(R.styleable.GradientTextView_endColor, 0xffd007);
        strokeColor = ta.getColor(R.styleable.GradientTextView_strokeColor, 0x2a1505);
        strokeWidth = ta.getDimension(R.styleable.GradientTextView_strokeWidth, 0);
        isStroke = ta.getBoolean(R.styleable.GradientTextView_isStroke, false);
        isGradient = ta.getBoolean(R.styleable.GradientTextView_isGradient, false);
        isVertical = ta.getBoolean(R.styleable.GradientTextView_isVertical, true);
        useTypeface = ta.getBoolean(R.styleable.GradientTextView_useTypeface, false);
        isShadow = ta.getBoolean(R.styleable.GradientTextView_isShadow, false);
        shadowX = ta.getInt(R.styleable.GradientTextView_shadowX, 1);
        shadowY = ta.getInt(R.styleable.GradientTextView_shadowY, 1);
        shadowRadius = ta.getInt(R.styleable.GradientTextView_shadowRadius, 1);
        shadowColor = ta.getColor(R.styleable.GradientTextView_shadowColor, 0x000000);
}

3、根据属性判断是否需要应用效果:

(1)描边

        这里简单说下描边的实现:其实就是绘制两个不一样大的TextView,大一点的就是描边的颜色,小一点的就是我们需要的字体的颜色,因此,我们需要创建一个新的TextView来实现描边:

if (isStroke) {
        backGroundText = new TextView(context, attrs);
        TextPaint tp1 = backGroundText.getPaint();
        tp1.setStrokeWidth(strokeWidth);
        tp1.setStyle(Paint.Style.STROKE);
        backGroundText.setTextColor(strokeColor);
        backGroundText.setGravity(getGravity());
        }

(2)渐变

        渐变的实现,其实就是设置一个Shader,也就是LinearGradient。说起Linear,我们就想起LinearLayout的Vertical和Horizontal属性,LinearGradient也需要一个标志着横向或者竖向的属性。因此,我们需要根据横向渐变还是竖向渐变去设置对应的shader:

if (isGradient) {
            if (isVertical) {
                mLinearGradient = new LinearGradient(0, 0, 0,
                        this.getPaint().getTextSize(),
                        startColor,
                        endColor,
                        Shader.TileMode.CLAMP);
            } else {
                mLinearGradient = new LinearGradient(0, 0, this.getWidth(),
                        0,
                        startColor,
                        endColor,
                        Shader.TileMode.CLAMP);
            }
        }

(3)阴影

        阴影的实现,不得不说,确实费了些时间。我们都知道TextView本身就支持阴影效果。但是,我们自定义的字体,如果直接使用自带的阴影效果,会非常模糊,根本不是阴影的效果。因此,阴影的效果也需要我们自己去实现。实现的要点是:为TextView设置Layer,在设置完后,必须清空Layer再去绘制其他的地方。在这里,我就直接把所有的绘制贴出来了:

@Override
    protected void onDraw(Canvas canvas) {
        if (isShadow) {
            getPaint().setShadowLayer(shadowRadius, shadowX, shadowY, shadowColor);
            getPaint().setShader(null);
            super.onDraw(canvas);

            getPaint().clearShadowLayer();
            getPaint().setShader(mLinearGradient);
            super.onDraw(canvas);
        } else {
            if (isStroke) {
                if (backGroundText != null) {
                    CharSequence tt = backGroundText.getText();
                    if (tt == null || !tt.equals(this.getText())) {
                        backGroundText.setText(getText());
                        this.postInvalidate();
                    }
                }
                backGroundText.draw(canvas);
            }
            if (isGradient) {
                this.getPaint().setShader(mLinearGradient);
            }
        }
        super.onDraw(canvas);
    }

大家看到上面的代码,可能有些疑惑,在这里简单的说明一下:

(1)如果直接应用TextView自带的阴影属性,会很模糊,其实是把渐变的shader作为了阴影。因此,我们设置layer,同时设置shader为空。

(2)设置完layer后,先绘制阴影。绘制完阴影后,清空layer,设置渐变的属性,继续绘制渐变的效果。

(3)如果我们的文本内容是会变化的,那么我们需要及时同步描边的字体和文本内容的一致。因此,判断有描边的属性时,我们需要做一下文本的同步。

三、在布局文件中使用

<com.example.mygradient.GradientTextView
        android:id="@+id/tv2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="12:45"
        android:textSize="18sp"
        app:endColor="#ffd007"
        app:isGradient="true"
        app:isStroke="false"
        app:isVertical="true"
        app:isShadow="true"
        app:shadowColor="#df000000"
        app:shadowRadius="1"
        app:shadowX="1"
        app:shadowY="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv1"
        app:startColor="#fffff8"
        app:useTypeface="false" />

        以上就是多效果文本框的实现,核心的代码都已经给出,需要说明的地方也已经在上面详细说明了。