源码链接
夸克网盘分享
效果展示
分析
- 动态效果,使用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;//绘制文本 计算baselinefloat 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;}@Overrideprotected 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;//绘制文本 计算baselinefloat 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);//时长2svalueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic 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;@Overrideprotected 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.ColorChangeTextViewandroid: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" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="左到右"android:onClick="leftToRight"/><Buttonandroid: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>