美文网首页已实践
自定义View_05(小试牛刀)字体变色

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

作者: __Y_Q | 来源:发表于2019-12-31 00:01 被阅读0次

    照旧, 先上图


    文字变色

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

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

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

    运用到的知识点

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

    正文:

    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 文件

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

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

    • 先在布局文件中引入我们的自定义控件, 并设置两个按钮, 添加点击事件
    <?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>
    
    • 开始在 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(小试牛刀)字体变色

        本文链接:https://www.haomeiwen.com/subject/usfvoctx.html