已实践

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

2019-12-31  本文已影响0人  __Y_Q

照旧, 先上图


文字变色

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

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

运用到的知识点


正文:

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

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

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

<resources>
    <declare-styleable name="MyTrackTextView">
        <attr name="originColor" format="color" />
        <attr name="changeColor" format="color" />
    </declare-styleable>
</resources>

2. 新建 MyTrackTextView.java 文件

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

@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. 先绘制一个原色的文本

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
     android:gravity="center">

    <org.zyq.MyTrackTextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:text="大牛之路,从小牛开始"
        android:textSize="20sp"
        app:changeColor="@color/colorPrimary"
        app:originColor="@color/colorAccent" />

    <Button
        android:onClick="leftToRight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="从左向右移动" />

    <Button
        android:onClick="rightToRight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="从右向左移动" />

</LinearLayout>
    @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. 让控件动起来(从左至右)

    private float mCurrentProgress = 0.0f;
    public void setCurrentProgress(float currentProgress) {
        this.mCurrentProgress = currentProgress;
        invalidate();
    }
    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地址稍后放出.

上一篇下一篇

猜你喜欢

热点阅读