自定义View-渐变TextView(重点:绘制文本)

效果展示


分析

  • 动态效果,使用Animator实现
  • 自定义View
    • 继承TextView
    • 使用TextView的测量,不重写
    • 使用TextView的布局,不重写
    • 绘制-重写绘制
      • 使用两种颜色绘制文本
      • 颜色占比不同,百分比从0~1

实现

自定义属性

从需求可知,需要定义两种颜色。

  • 在attrs.xml中使用declare-styleable标签声明属性。

  • 自定义View.java中使用TypedArray获取xml属性值(在构造函数中获取)

        注意:获取值后,需要使用typedArray.recycle()回收。

  • xml中使用自定义View及其自定义属性


创建画笔

绘制,就是在画布上用画笔绘制内容。

要绘制两种颜色的文字,需要两支画笔,画笔有不同的颜色和大小,使用颜色的大小创建画笔。


绘制文字

画布Canvas类中提供了绘制文字的方法。

text:要绘制的文本。--继承自TextView,通过getText().toString()获取。

x:绘制文本水平方向的开始位置。 -- 可以从0开始,

y:文字的基线baseline。

paint:画笔(颜色、大小)-- 已创建

  • 什么是基线?

来源于四线三格。

基线就是第三条线。

  • Paint.FontMetrics类说明

Paint.FontMetrics类说明,通过paint.getFontMetrics()获取。

红色框是mPaint.getTextBounds(textStr,0,textStr.length(),rect1),获取文字的Rect。就是图中的Rect r。

蓝色的框是文字真实大小,去除padding的。

通过paint.getFontMetrics()获取的是图中FontMetricInt的值,都是以Baseline为起点获取的值,向上是负值(top),向下是正值(bottom)。

  • 基线的计算方法?

获取基线的方式有多种,此处使用FontMetrics的top、bottom以及getHeight()获取基线。

先获取下图dy的高度,然后使用高度中线(即图中红线的位置 高度/2)+dy获取基线的坐标。

获取dy的方法:float dy = (fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom

基线位置1:baseline = getHeight()/2+dy 注意:此方法文本永远在TextView中居中,即使paddingTop和paddingBottom值不一致的时候。

基线位置2:float baseline = (fontMetrics.bottom-fontMetrics.top)/2+dy+getPaddingTop(); 此方法考虑了paddingTop和paddingBottom值不一致的时候


裁剪画布-如何绘制部分文字

那么如何将文字分为两部分绘制,且使用不同的画笔。

裁剪画布,在onDraw(Canvas canvas)中是的画布大小是View所能使用的整个区域,要分左右两部分绘制,那就通过裁剪画布的方式实现,将画布裁剪为左右两部分,分别使用不同的画笔绘制。

  • 裁剪画布前先创建画布副本 canvas.save(); 这样画布可以恢复。
  • 创建画布副本后,绘制的内容,在画布清空后,已绘制的内容仍然生效。
  • 画布可以分层
  • 超出画布大小的部分,即使绘制,也不显示。
  • 清空画布 canvas.restore();

//两种颜色各绘制一半
int point = (int) (getWidth()*0.5f);
drawText(canvas, mChangePaint, 0, point);
drawText(canvas, mOriginalPaint, point, getWidth());

/**
 * 绘制文本
 * 创建画布副本
 * 裁剪画布
 * 绘制文本 只能绘制画布大小,超出的部分不绘制,画布清空后已绘制的内容仍存在
 * 清空画布 恢复画布大小/清空画布内容
 */
private void drawText(Canvas canvas, Paint paint,int start,int end) {
    //创建画布副本
    canvas.save();

    //将画布裁剪为Rect大小
    Rect rect = new Rect(start,0,end,getHeight());
    canvas.clipRect(rect);

    String text = getText().toString();
    //判空
    if(TextUtils.isEmpty(text)) return;

    //绘制文本 计算baseline
    float dy = 0;
    Paint.FontMetrics fontMetrics = paint.getFontMetrics();
    dy = (fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;

    float baseline = getHeight()/2+dy;

    canvas.drawText(text,0,baseline,paint);

    //将画布清空(内容和大小)
    canvas.restore();
}

设置颜色占比

需将两种颜色绘制文字的占比,能自定义,范围是0~1,动态设置占比。、

注意:在设置后,需要刷新重绘,否则不生效。


设置颜色变化方向

为了实现从左到右变化,和从右到左变化,需要设置方向。


动画实现动效

使用动画,实现颜色占比0~1的变化。


源码

package com.learn.customui.custom;

import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;

import com.learn.customui.R;

public class ColorChangeTextView extends AppCompatTextView {
    //原始颜色画笔
    private Paint mOriginalPaint;
    //结束颜色画笔
    private Paint mChangePaint;
    //颜色占比
    private float mCurrentProgress = 0f;
    //方向
    private Direction mDirection = Direction.LEFT_TO_RIGHT;

    public enum Direction{
        LEFT_TO_RIGHT,RIGHT_TO_LEFT
    }

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

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

    public ColorChangeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    //初始化View 获取属性值
    private void init(Context context,AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorChangeTextView);
        int originalColor = typedArray.getColor(R.styleable.ColorChangeTextView_originalColor, Color.BLACK);
        int changeColor = typedArray.getColor(R.styleable.ColorChangeTextView_changeColor,Color.BLACK);

        mOriginalPaint = getPaint(originalColor);
        mChangePaint = getPaint(changeColor);

        //一定要回收?为什么?
        typedArray.recycle();
    }

    //创建Paint 颜色、大小
    private Paint getPaint(int color){
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setTextSize(getTextSize());
        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int point = (int) (getWidth()*mCurrentProgress);
        if(mDirection == Direction.LEFT_TO_RIGHT) {
            drawText(canvas, mChangePaint, 0, point);
            drawText(canvas, mOriginalPaint, point, getWidth());
        }else if(mDirection == Direction.RIGHT_TO_LEFT){
            drawText(canvas, mOriginalPaint, 0, getWidth()-point);
            drawText(canvas, mChangePaint, getWidth()-point, getWidth());
        }
    }

    /**
     * 绘制文本
     * 创建画布副本
     * 裁剪画布
     * 绘制文本 只能绘制画布大小,超出的部分不绘制,画布清空后已绘制的内容仍存在
     * 清空画布 恢复画布大小/清空画布内容
     */
    private void drawText(Canvas canvas, Paint paint,int start,int end) {
        //创建画布副本
        canvas.save();

        //将画布裁剪为Rect大小
        Rect rect = new Rect(start,0,end,getHeight());
        canvas.clipRect(rect);

        String text = getText().toString();
        //判空
        if(TextUtils.isEmpty(text)) return;

        //绘制文本 计算baseline
        float dy = 0;
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        dy = (fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;

        float baseline = getHeight()/2+dy;

        canvas.drawText(text,0,baseline,paint);

        //将画布清空(内容和大小)
        canvas.restore();
    }

    //设置颜色改变点
    public void setCurrentProgress(float mCurrentProgress) {
        this.mCurrentProgress = mCurrentProgress;
        //刷新 重绘 否则动画不生效
        invalidate();
    }

    //设置颜色改变方向
    public void setDirection(Direction mDirection) {
        this.mDirection = mDirection;
    }


    //设置动画 实现动态改变文字颜色的过程
    public void setAnimator(ColorChangeTextView.Direction direction){
        setDirection(direction);
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);//值变化范围
        valueAnimator.setDuration(2000);//时长2s
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentProgress = (float)animation.getAnimatedValue();
                setCurrentProgress(currentProgress);
            }
        });
        valueAnimator.start();
    }
}
package com.learn.customui;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;

import com.learn.customui.custom.ColorChangeTextView;

public class MainActivity extends AppCompatActivity {

    ColorChangeTextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = (ColorChangeTextView) findViewById(R.id.tv);

    }

    public void leftToRight(View view) {
        tv.setAnimator(ColorChangeTextView.Direction.LEFT_TO_RIGHT);
    }

    public void rightToLeft(View view) {
        tv.setAnimator(ColorChangeTextView.Direction.RIGHT_TO_LEFT);
    }



}
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.learn.customui.custom.ColorChangeTextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/red"
        android:paddingTop="10dp"
        android:paddingBottom="5dp"
        android:text="中华人民共和国"
        app:originalColor="@color/black"
        app:changeColor="@color/red" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="左到右"
        android:onClick="leftToRight"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右到左"
        android:onClick="rightToLeft"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ColorChangeTextView">
        <attr name="originalColor" format="color"/>
        <attr name="changeColor" format="color"/>
    </declare-styleable>
    
</resources>

相关推荐

  1. 定义View

    2024-07-11 02:36:02       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 02:36:02       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 02:36:02       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 02:36:02       57 阅读
  4. Python语言-面向对象

    2024-07-11 02:36:02       68 阅读

热门阅读

  1. 在Ubuntu 14.04上安装和配置VNC的方法

    2024-07-11 02:36:02       25 阅读
  2. iOS 应用内存超过多少会收到系统内存警告 ?

    2024-07-11 02:36:02       25 阅读
  3. 怎么在windows、linux、mac上安装pnpm呢?

    2024-07-11 02:36:02       26 阅读
  4. 数据结构与算法基础篇--二分查找

    2024-07-11 02:36:02       20 阅读
  5. Redis原理-数据结构

    2024-07-11 02:36:02       25 阅读
  6. ArduPilot开源代码之AP_OpticalFlow_CXOF

    2024-07-11 02:36:02       28 阅读
  7. QT实现WebSocket通信

    2024-07-11 02:36:02       24 阅读
  8. Text2SQL提问中包括时间的实战方案

    2024-07-11 02:36:02       22 阅读
  9. 进程与线程的区别

    2024-07-11 02:36:02       23 阅读