Android 自定义UI 实战 01 文字变色
Android 自定义 UI 实战
第一章: 自定义View 文字变色
@TOC
前言
使用纯代码 加 注释的方式,可以更快的理解源码
 如果你喜欢,请点个赞,后期会不断的深入讲解
使用自定义UI 完成 歌词文字颜色加载效果
1、自定义文本属性
在项目attrs.xml 文件中 res -> values -> attrs.xml,自定义文本属性,没有这个文件,就新建一个。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ColorTrackTextView">
        <attr name="originColor" format="color" />
        <attr name="changeColor" format="color" />
    </declare-styleable>
</resources>
2、创建组件
新建一个 ColorTrackTextView 文件,继承自 AppCompatTextView 这里也可以直接继承View,我只需要自定义TextView ,就直接继承 AppCompatTextView
public class ColorTrackTextView extends AppCompatTextView {
//    绘制不变色字体的画笔
    private Paint mOriginPaint;
//    绘制变色字体的画笔
    private Paint mChangePaint;
    public ColorTrackTextView(@NonNull Context context) {
        this(context, null);
    }
    public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
3、在 xml 中使用组件
<com.traveleasy.leaningui.ColorTrackTextView
       android:id="@+id/color_track_tv"
       android:text="Hello Word"
       android:textSize="20sp"
       app:changeColor="@color/teal_200"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       tools:ignore="MissingConstraints" />
上面的三步中,已经完成了自定义TextView 的属性设置,以及在 xml 中使用,接下来开始做测量绘制的效果
3、测量
这里我直接继承的是 AppCompatTextView, 所以就直接跳过了测量部分。当然,如果继承的是View,也只需要测量自己就可以了
4、绘制
接下来通过 onDraw:绘制,在此之前,我们需要先创建一个画笔
  /**
     * 根据颜色,设置画笔
     *
     * @return
     */
    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
//        设置颜色
        paint.setColor(color);
//       设置抗锯齿
        paint.setAntiAlias(true);
//       防抖动
        paint.setDither(true);
//       设置字体的大小 就是TextView 文本字体大小
        paint.setTextSize(getTextSize());
        return paint;
    }
画笔中的颜色,我传入即可
 这里我初始了两个画笔颜色,一个默认颜色,和一个需要变成的颜色
//    绘制不变色字体的画笔
    private Paint mOriginPaint;
//    绘制变色字体的画笔
    private Paint mChangePaint;
需要先初始化一个 TypedArray,在使用 TypedArray 时候,别忘了回收哦
    private void initPaint(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorTrackTextView);
        int changeColor = typedArray.getColor(R.styleable.ColorTrackTextView_changeColor, getTextColors().getDefaultColor());
        int originColor = typedArray.getColor(R.styleable.ColorTrackTextView_originColor, getTextColors().getDefaultColor());
//        typedArray 回收
        typedArray.recycle();
//        不变颜色的画笔
        mOriginPaint = getPaintByColor(originColor);
//        变色的画笔
        mChangePaint = getPaintByColor(changeColor);
    }
接下来就是绘制部分了,绕了一大圈,在这里,先看一张图,如下:
 
如图所示,这里我们需要先获取文本内容的基线 BaseLine,具体的就不解释了,不懂的地方请留言,直接看绘制代码吧。
 /**
     * 绘制部分
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
//        获取绘制文本内容
        String text = getText().toString();
//        判空
        if (TextUtils.isEmpty(text)) return;
//        获取文字区域
        Rect bounds = new Rect();
        mOriginPaint.getTextBounds(text, 0, text.length(), bounds);
//        获取 x 坐标 (这里除 2 是为了让文本居中)
       int dx = getWidth() / 2 - bounds.width() / 2;
//       获取基线 baseLine
        Paint.FontMetricsInt fontMetricsInt = mChangePaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
//        绘制文字
        canvas.drawText(text, dx, baseLine, mOriginPaint);
    }
5、初步绘制效果
至此就完成了一个UI的基本绘制,看下效果图。

什么?这就完了?说好的变色呢?请继续往下看
6、绘制变色部分
声明一个变色进度的变量,这里默认值设置一半
  // 当前变色的进度
    private float mCurrentProgress = 0.5f;
接下来我们修改一下代码
 /**
     * 绘制部分
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
      int currentPoint = (int) (mCurrentProgress * getWidth());
        onDrawText(canvas, mOriginPaint, 0, currentPoint);
        onDrawText(canvas, mChangePaint, currentPoint, getWidth());
    }
    /**
     *
     * @param canvas
     * @param paint
     * @param start    起始位置
     * @param end      结束位置
     */
    private void onDrawText(Canvas canvas, Paint paint, int start, int end){
        canvas.save();
        Rect rect = new Rect(start, 0, end, getHeight());
//        这里就要用到裁切 只显示裁切后的内容
        canvas.clipRect(rect);
//        获取绘制文本内容
        String text = getText().toString();
//        判空
        if (TextUtils.isEmpty(text)) return;
//        获取文字区域
        Rect bounds = new Rect();
        mOriginPaint.getTextBounds(text, 0, text.length(), bounds);
//        获取 x 坐标 (这里除 2 是为了让文本居中)
        int dx = getWidth() / 2 - bounds.width() / 2;
//       获取基线 baseLine
        Paint.FontMetricsInt fontMetricsInt = mChangePaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
//        绘制文字
        canvas.drawText(text, dx, baseLine, paint);
        canvas.restore();
    }
这样我们就初步实现了文字变色的功能

7、自动变色功能实现
在XML 中新增两个按钮,控制变色的方向
<Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:onClick="leftToRight"
       android:text="左到右"
       tools:ignore="MissingConstraints,UsingOnClickInXml" />
   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:onClick="rightToLeft"
       android:text="右到左"
       tools:ignore="MissingConstraints,UsingOnClickInXml" />
定义一个变量,实现不同方向
    //    实现不同的朝向
    private Direction mDirection;
    //    定义一个枚举,保存两个方向
    public enum Direction {
        LEFT_TO_RIGHT, RIGHT_TO_LEFT
    }
修改onDraw 中的代码,实现不同方向的变色绘制
 /**
     * 绘制部分
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        int currentPoint = (int) (mCurrentProgress * getWidth());
//        从左边开始回事
        if (mDirection == Direction.LEFT_TO_RIGHT) {
//            绘制变色的部分,--  开始 currentPoint = 0, 结束 currentPoint = getWidth
            onDrawText(canvas, mChangePaint, 0, currentPoint);
//            绘制不变色的部分
            onDrawText(canvas, mOriginPaint, currentPoint, getWidth());
        } else {
//            绘制变色的部分 --   开始 currentPoint = getWidth, 结束 currentPoint = 0
            onDrawText(canvas, mChangePaint, getWidth() - currentPoint, getWidth());
//            绘制不变色的部分
            onDrawText(canvas, mOriginPaint, 0, getWidth() - currentPoint);
        }
    }
在activity 中实现点击控制事件
    public void leftToRight(View view) {
        setAnimation(ColorTrackTextView.Direction.LEFT_TO_RIGHT);
    }
    public void rightToLeft(View view) {
        setAnimation(ColorTrackTextView.Direction.RIGHT_TO_LEFT);
    }
setAnimation() 加载进度写的动画控制
 public void setAnimation(ColorTrackTextView.Direction direction){
        mColorTrackTextView.setDirection(direction);
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                float currentProgress = (float) animator.getAnimatedValue();
                mColorTrackTextView.setCurrentProgress(currentProgress);
            }
        });
        valueAnimator.start();
    }
至此便完成了自定义 UI 文本 变化的功能实现,看下效果图

完整代码如下:
package com.traveleasy.leaningui;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
public class MainActivity extends AppCompatActivity {
    private ColorTrackTextView mColorTrackTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        mColorTrackTextView = findViewById(R.id.color_track_tv);
    }
    public void leftToRight(View view) {
        setAnimation(ColorTrackTextView.Direction.LEFT_TO_RIGHT);
    }
    public void rightToLeft(View view) {
        setAnimation(ColorTrackTextView.Direction.RIGHT_TO_LEFT);
    }
    public void setAnimation(ColorTrackTextView.Direction direction){
        mColorTrackTextView.setDirection(direction);
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                float currentProgress = (float) animator.getAnimatedValue();
                mColorTrackTextView.setCurrentProgress(currentProgress);
            }
        });
        valueAnimator.start();
    }
}
package com.traveleasy.leaningui;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
public class ColorTrackTextView extends AppCompatTextView {
    //    绘制不变色字体的画笔
    private Paint mOriginPaint;
    //    绘制变色字体的画笔
    private Paint mChangePaint;
    //    当前变色的进度
    private float mCurrentProgress = 0.5f;
    //    实现不同的朝向
    private Direction mDirection;
    //    定义一个枚举,保存两个方向
    public enum Direction {
        LEFT_TO_RIGHT, RIGHT_TO_LEFT
    }
    public ColorTrackTextView(@NonNull Context context) {
        this(context, null);
    }
    public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context, attrs);
    }
    private void initPaint(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorTrackTextView);
        int changeColor = typedArray.getColor(R.styleable.ColorTrackTextView_changeColor, getTextColors().getDefaultColor());
        int originColor = typedArray.getColor(R.styleable.ColorTrackTextView_originColor, getTextColors().getDefaultColor());
//        typedArray 回收
        typedArray.recycle();
//        不变颜色的画笔
        mOriginPaint = getPaintByColor(originColor);
//        变色的画笔
        mChangePaint = getPaintByColor(changeColor);
    }
    /**
     * 根据颜色,设置画笔
     *
     * @return
     */
    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
//        设置颜色
        paint.setColor(color);
//       设置抗锯齿
        paint.setAntiAlias(true);
//       防抖动
        paint.setDither(true);
//       设置字体的大小 就是TextView 文本字体大小
        paint.setTextSize(getTextSize());
        return paint;
    }
    /**
     * 绘制部分
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        int currentPoint = (int) (mCurrentProgress * getWidth());
//        从左边开始回事
        if (mDirection == Direction.LEFT_TO_RIGHT) {
//            绘制变色的部分,--  开始 currentPoint = 0, 结束 currentPoint = getWidth
            onDrawText(canvas, mChangePaint, 0, currentPoint);
//            绘制不变色的部分
            onDrawText(canvas, mOriginPaint, currentPoint, getWidth());
        } else {
//            绘制变色的部分 --   开始 currentPoint = getWidth, 结束 currentPoint = 0
            onDrawText(canvas, mChangePaint, getWidth() - currentPoint, getWidth());
//            绘制不变色的部分
            onDrawText(canvas, mOriginPaint, 0, getWidth() - currentPoint);
        }
    }
    /**
     * @param canvas
     * @param paint
     * @param start  起始位置
     * @param end    结束位置
     */
    private void onDrawText(Canvas canvas, Paint paint, int start, int end) {
        canvas.save();
        Rect rect = new Rect(start, 0, end, getHeight());
//        这里就要用到裁切 只显示裁切后的内容
        canvas.clipRect(rect);
//        获取绘制文本内容
        String text = getText().toString();
//        判空
        if (TextUtils.isEmpty(text)) return;
//        获取文字区域
        Rect bounds = new Rect();
        mOriginPaint.getTextBounds(text, 0, text.length(), bounds);
//        获取 x 坐标 (这里除 2 是为了让文本居中)
        int dx = getWidth() / 2 - bounds.width() / 2;
//       获取基线 baseLine
        Paint.FontMetricsInt fontMetricsInt = mChangePaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
//        绘制文字
        canvas.drawText(text, dx, baseLine, paint);
        canvas.restore();
    }
    public void setCurrentProgress(float currentProgress) {
        this.mCurrentProgress = currentProgress;
        invalidate();
    }
    public void setDirection(Direction direction) {
        this.mDirection = direction;
    }
}
总结
一.文字变色 – 自定义View
1.自定义属性,以及xml中使用
2.测量 — 只需要测量自己
3.onDraw:绘制自己
4.交互FontMetricInt.top 负数 FontMetricInt.bottom 正数
裁剪,只显示 裁剪后 canvas.drawXxx 的内容 cavas.clipRect();
- 点赞
- 收藏
- 关注作者
 
             
           
评论(0)