自定义View_05(小试牛刀)字体变色

照旧, 先上图


文字变色

这是一个字体变色的 Demo, 主要还是练习 onDraw 方法.
实现思路

  • 两个画笔,一个原色画笔, 一个变色画笔
  • 一个和文本宽度相关的进度值
  • 先用原色画笔绘制出文本
  • 不断改变进度值, 原色画笔不断向右进行裁剪,把左边的裁剪掉,
  • 原色画笔不断裁剪的同时, 变色画笔不断根据同一个进度值进行绘制

例如当前进度值是50%, 就是文本的一半, 就是在 "大牛之路," 这个后面.
原色画笔向右裁剪50%, 显示的就是 "从小牛开始"这几个字, 前面几个字被裁剪掉了.
然后变色画笔开始绘制, 从左向右绘制 50%, 就是 "大牛之路," 这几个字. 后面几个字被裁剪掉了.
组合起来,这样 "大牛之路," 与 "从小牛开始" 字体的颜色就会不相同了. 不断改变进度值,就可以实现上图效果.

运用到的知识点

  • Canvas 的裁剪 (学习目标)
  • 绘制文本基线的计算

正文:

1. 创建自定义属性文件 attrs.xml

自定义属性说明:
(因为我们的自定义控件是继承自 TextView, TextView 自身的属性已经够我们使用的了, 所以这里我们就定义两个属性就够了.)

MyTrackTextView: 我们自定义 View 的名字
originColor: 表示原色
changeColor: 表示改变的颜色


    
        
        
    

2. 新建 MyTrackTextView.java 文件

建好文件后, 需要做以下几件事情

  • 继承 TextView,
  • 重写三个构造函数
  • 重写 onDraw 方法.
  • 获取我们上面定义的两个自定义属性.
  • 初始化两个画笔(原色的和需要改变的颜色的)
@SuppressLint("AppCompatCustomView")
public class MyTrackTextView extends TextView {

    private Paint mOriginPaint, mChangePaint;

    public MyTrackTextView(Context context) {
        this(context, null);
    }

    public MyTrackTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTrackTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 获取自定义属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTrackTextView);
        int originColor = typedArray.getColor(R.styleable.MyTrackTextView_originColor, getTextColors().getDefaultColor());
        int changeColor = typedArray.getColor(R.styleable.MyTrackTextView_changeColor, getTextColors().getDefaultColor());
        mOriginPaint = getPaintByColor(originColor);
        mChangePaint = getPaintByColor(changeColor);
        // 回收
        typedArray.recycle();
    }

    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setAntiAlias(true);
        //防抖动
        paint.setDither(true);
        paint.setTextSize(getTextSize());
        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

OK, 准备工作完成.

3. 先绘制一个原色的文本

  • 先在布局文件中引入我们的自定义控件, 并设置两个按钮, 添加点击事件



    

    
  • 开始在 onDraw 方法中绘制原色文本
    @Override
    protected void onDraw(Canvas canvas) {
        //不要继承父类的.super.onDraw
        //传入画布与画笔
        drawText(canvas, mOriginPaint);
    }

    private void drawText(Canvas canvas, Paint paint) {
        //获取字体的宽度,宽度的一半减去文字的一半,得到开始位置
        String text = getText().toString();
        Rect bounds = new Rect();
        mOriginPaint.getTextBounds(text, 0, text.length(), bounds);
        int x = getWidth() / 2 - bounds.width() / 2;

        //基线
        Paint.FontMetricsInt metricsInt = paint.getFontMetricsInt();
        int dy = (metricsInt.bottom - metricsInt.top) / 2 - metricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;

        //开始画
        canvas.drawText(text, x, baseLine, paint);
    }

运行效果如下


原色运行效果

4. 开始使用裁剪方法 canvas.clipRect()

canvas.clipRect(int left, int top, int right, int bottom)

这个方法大概意思就是说,使用矩形进行裁剪. 矩形以本地坐标表示
left: 区域的起始坐标
top: 区域的顶部坐标
right: 区域的右侧坐标
bottom: 区域的底部坐标
这4个坐标设置完后, 刚好构成了一个正方形/长方形, 保留这个区域内的内容, 裁剪区域外的内容, ......理解起来有点费劲. 其实就是裁剪的是这个区域外的内容. 直接理解为,这个区域内是要保留的,剩下的都裁剪掉


网上找的图

由left和top生成一个点,right和bottom生成一个点,然后取这2个点的交集就生成了蓝色区域(裁剪之后的图片),

我们先随便设置一个矩形, 看一下效果.

    @Override
    protected void onDraw(Canvas canvas) {
        //不要继承父类的.super.onDraw
        canvas.clipRect(getWidth() / 2, 0, getWidth(), getHeight());
        //传入画布与画笔
        drawText(canvas, mOriginPaint);
    }

canvas.clipRect(getWidth() / 2, 0, getWidth(), getHeight());
保留的区域为:
起始位置: 文本宽度的一半
顶部位置: 0
截止位置: 文本的宽度
底部位置: 文本的高度距离
意思是保留的文本从一半到最后, 其余的裁剪掉.

效果如下:


文本中间到最后的裁剪

我这里是为了测试, 所以写了固定的值, 实际上起始位置的值和截止位置的值都是动态改变的. 我们需要设置一个变量来保存当前进度

    private float mCurrentProgress = 0.5f;

那么我们改造一下 onDrawdrawText 方法,使其变得更通用,增加两个参数,起始位置和结束的位置

    @Override
    protected void onDraw(Canvas canvas) {
        //不要继承父类的.super.onDraw
        //根据进度把要移动的值算出来
        int moveValue = (int) (mCurrentProgress * getWidth());
        //传入画布, 画笔, 起始位置, 结束位置
        drawText(canvas, mOriginPaint, moveValue, getWidth());
    }
    private void drawText(Canvas canvas, Paint paint, int start, int end) {
        //裁剪区域为文本start位置到end位置之外的区域.
        canvas.clipRect(start, 0, end, getHeight());
        //绘制文本
        .....
    }

5. 开始绘制变色文本

    @Override
    protected void onDraw(Canvas canvas) {
        //不要继承父类的.super.onDraw
        //根据进度把要移动的值算出来
        int moveValue = (int) (mCurrentProgress * getWidth());
        //传入画布, 画笔, 起始位置, 结束位置, 绘制原色文本
        drawText(canvas, mOriginPaint, moveValue, getWidth());
        //传入画布, 画笔, 起始位置, 结束位置, 绘制变色文本
        drawText(canvas, mChangePaint, 0, moveValue);
    }

原色文本的裁剪区域是文本左边的一半,那么变色文本的裁剪区域就是文本右边的一半,

运行后,我们发现,并没有变色,是为什么呢.因为画了原色后,画布并没有释放, 所以变色的才会没有效果.
drawText 方法的开始和结尾需要加上

    private void drawText(Canvas canvas, Paint paint, int start, int end) {
        //保存画布
        canvas.save();
        //裁剪区域为文本start位置到end位置之外的区域.
        ...
        //绘制文本
        .....
        //释放画布
        canvas.restore();
    }

save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。

运行效果如下:


变色文本

那么剩下的就简单了, 我们只需要在外部不断的改变 mCurrentProgress 这个值, 就可以了. 先让控件动起来, 再考虑方向的问题.

6. 让控件动起来(从左至右)

  • 在自定义控件内对 mCurrentProgress 属性添加set方法, 可以让外部调用, 并且不断绘制.
  • mCurrentProgress值设置为 0.0f
    private float mCurrentProgress = 0.0f;
    public void setCurrentProgress(float currentProgress) {
        this.mCurrentProgress = currentProgress;
        invalidate();
    }
  • 在 mainActivity 中 leftToRight 点击事件中使用属性动画, 让控件动起来.
    private MyTrackTextView myTrackTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myTrackTextView = findViewById(R.id.textview);
    }

    public void leftToRight(View view) {
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                myTrackTextView.setCurrentProgress(value);
            }
        });
        valueAnimator.start();
    }

怎么样, 到这一步,是不是已经动起来了? 下一步就是改变一下方向, 从右至左.

7.从右至左

我们需要在自定义控件中设置一个方向的属性, 来表示是从左至右,还是从右至左.,并设置它的set方法

    //不同方向,左,右, 默认从左至右
    private Orientation mOrientation = Orientation.LEFT_TO_RIGHT;

    public enum Orientation {
        LEFT_TO_RIGHT,
        RIGHT_TO_LEFT
    }

    public void setOrientation(Orientation orientation) {
        this.mOrientation = orientation;
    }

    @Override
    protected void onDraw(Canvas canvas) {
       // super.onDraw(canvas);
        //不要继承父类的.super.onDraw
        // 根据进度把要移动的值算出来
        int moveValue = (int) (mCurrentProgress * getWidth());
        //如果是从左向右
        if (mOrientation == Orientation.LEFT_TO_RIGHT) {
            //1.绘制变色的
            drawText(canvas, mChangePaint, 0, moveValue);
            //2.绘制原色的
            drawText(canvas, mOriginPaint, moveValue, getWidth());
        } else {
            //1.绘制原色的
            drawText(canvas, mOriginPaint, getWidth() - moveValue, getWidth());
            //2.绘制变色的
            drawText(canvas, mChangePaint, 0, getWidth() - moveValue);
        }
    }

从左向右,我们已经明白了.
从右向左,
原色: drawText(canvas, mOriginPaint, getWidth() - moveValue, getWidth());
变色: drawText(canvas, mChangePaint, 0, getWidth() - moveValue);
下面一张图应该很直观. (略丑)

从右向左

8. 让从右向左变色也动起来.

    public void leftToRight(View view) {
        myTrackTextView.setOrientation(MyTrackTextView.Orientation.LEFT_TO_RIGHT);
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                myTrackTextView.setCurrentProgress(value);
            }
        });
        valueAnimator.start();
    }

    public void rightToRight(View view) {
        myTrackTextView.setOrientation(MyTrackTextView.Orientation.RIGHT_TO_LEFT);
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                myTrackTextView.setCurrentProgress(value);
            }
        });
        valueAnimator.start();
    }

收工, github地址稍后放出.

你可能感兴趣的:(自定义View_05(小试牛刀)字体变色)