前言:
最近公司要求实现一个 讯飞语音阅读文字,文字根据阅读速度逐个变色的功能。先上个图看下效果。
(由于工作非常紧张,所以就把测试的图贴过来了,兄弟们将就看)
直接上代码:
ColorTrackView.java(主要就是这个自定义控件)
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.yiwei.lib_common.R;
import java.util.ArrayList;
import java.util.List;
/**
* @param
* @author gaoql
* @description 逻辑: 主要核心是 canvas.clipRect 裁剪画布,比如文字 123456 通过裁剪 可以先将1的左前半部分
* 变色,同时通过裁剪把不变色的部分也画出来,这样就会出现一种歌词变色的效果(就是裁剪文字的后半部分)
* @return
* @time 2021/4/12 16:04
*/
public class ColorTrackView extends View {
private int mTextStartX = 0;
private int mTextTopX = 0;//裁剪距顶部的距离
private Paint mPaint = new Paint();
private String mText;
private int mTextSize = 30;
private int mTextOriginColor = 0xff000000;
private int mTextChangeColor = 0xffff0000;
private Rect mTextBound = new Rect();
private int mTextWidth = 0;
private int mTextTotalWidth = 0;//文字总长度
private int mTextHeight = 0;
private int mRowHeight = 70;//每行的高度
private int currentDrawRowHeight = mRowHeight;//当前画的行高
private int mTextBaseLineHeight = 60;//文字绘画基线高度
private int mTextY = mTextBaseLineHeight;
public int mProgress = -1;
private float stepLength;
public enum Direction {
LEFT, RIGHT;
}
private int mDirection = DIRECTION_LEFT;
private static final int DIRECTION_LEFT = 0;
private static final int DIRECTION_RIGHT = 1;
public ColorTrackView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorTrackView);
mText = ta.getString(R.styleable.ColorTrackView_text);
mTextSize = ta.getDimensionPixelSize(R.styleable.ColorTrackView_text_size, mTextSize);
mTextOriginColor = ta.getColor(R.styleable.ColorTrackView_text_origin_color, mTextOriginColor);
mTextOriginColor = ta.getColor(R.styleable.ColorTrackView_text_origin_color, mTextOriginColor);
mRowHeight = ta.getColor(R.styleable.ColorTrackView_row_height, 70);
//mProgress = ta.getFloat(R.styleable.ColorTrackView_progress, mProgress);
mDirection = ta.getInt(R.styleable.ColorTrackView_direction, mDirection);
ta.recycle();
mPaint.setTextSize(mTextSize);
mPaint.setAntiAlias(true);
mTextTotalWidth = (int) mPaint.measureText(mText);
mPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
//measureText();
stepLength = (float) mTextTotalWidth / 100f;//每段颜色变化长度
Rect rect = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), rect);
mTextBaseLineHeight = rect.height();//文字高
System.out.println("播放总长度:" + mTextTotalWidth);
System.out.println("每段播放长度:" + stepLength);
System.out.println("文字高度:" + mTextHeight);
}
/**
* @param
* @return
* @description 测量文字宽和高
* @author gaoql
* @time 2021/4/13 15:48
*/
/* private void measureText()
{
//得到文字的宽度
mTextWidth = (int) mPaint.measureText(mText);
mPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
Rect rect = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), rect);
mTextHeight = rect.height();//文字高
//System.out.println("mTextHeight:"+mTextHeight);
}*/
private void measureText(String mText) {
//得到文字的宽度
mTextWidth = (int) mPaint.measureText(mText);
//System.out.println("mTextHeight:"+mTextHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawText(canvas);
}
private int nowDrawRow = 0;//当前正在画的行数
private float mTextEndX = 0;//当前正在画的行文字宽度
private void drawText(Canvas canvas) {
//每行文字
String nowDrawText = "";
if (rowTextList != null) {
nowDrawText = rowTextList.get(nowDrawRow);
}
//画出背景文字
int bgTextBaseLine = mTextBaseLineHeight;
int bgTextX = 0;
for(int i=0;i<rowTextList.size();i++){
mPaint.setColor(mTextOriginColor);
canvas.drawText(rowTextList.get(i), bgTextX, bgTextBaseLine, mPaint);
bgTextBaseLine = bgTextBaseLine + mRowHeight;
}
//测量每行宽度
mTextWidth = (int) mPaint.measureText(nowDrawText);
/**
* 通过for循环 画出多行
*/
if (nowDrawRow > 0) {
int overDrawTextY = mTextBaseLineHeight;//之前画过的 文字y
for (int i = 0; i < nowDrawRow; i++) {
mPaint.setColor(mTextChangeColor);
canvas.drawText(rowTextList.get(i), 0, overDrawTextY, mPaint);
overDrawTextY = overDrawTextY + mRowHeight;
}
}
Rect rect = new Rect();
rect.left = 0;
rect.top = mTextTopX;
rect.right = (int)mTextEndX;
rect.bottom = currentDrawRowHeight;
//画带颜色的文字 根据mProgress的进度
drawText(canvas,
nowDrawText,
mTextChangeColor,
rect);
//判断当前行是否画完
if (mTextEndX >= mTextWidth) {
//画下一行数据
nowDrawRow++;
mTextEndX = 0;
mTextTopX = currentDrawRowHeight;
mTextY = currentDrawRowHeight + mTextBaseLineHeight;
currentDrawRowHeight = currentDrawRowHeight + mRowHeight;//增加文字高度 为画下一行提供数值
}
//裁剪长度
mTextEndX = mTextEndX + stepLength;
}
/**
* @description 清空带颜色的绘画
* @param
* @return
* @author gaoql
* @time 2021/4/14 10:56
*/
private void clearText(){
nowDrawRow = 0;
mTextEndX = stepLength;
mTextY = mTextBaseLineHeight;
mTextTopX = 0;
currentDrawRowHeight = mRowHeight;
}
private void drawOriginLeft(Canvas canvas) {
//画除了颜色之外的 字体
/* drawText(canvas,
"",
mTextOriginColor,
(int) (mTextStartX + mProgress * mTextWidth),
mTextStartX +mTextWidth );*/
}
private void drawText(Canvas canvas, String drawText, int color, Rect clipRect) {
mPaint.setColor(color);
canvas.save();
canvas.clipRect(clipRect);
canvas.drawText(drawText, clipRect.left, mTextY, mPaint);
canvas.restore();
// canvas.save(); 和 canvas.restore(); 的作用,保存之前画的效果, 继续画,画完之后取出之前的效果 合并
}
private List<String> rowTextList;//每行的数据
private int viewWidth;//当前view的宽度
private int viewHeight;//当前view的高度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = measureWidth(widthMeasureSpec);//得到View的宽
viewHeight = measureHight(heightMeasureSpec);//得到View的高
System.out.println("====width:" + viewWidth);
System.out.println("====height:" + viewHeight);
setMeasuredDimension(viewWidth, viewHeight);
System.out.println("mTextWidth:" + mTextWidth);
/*//通过view宽度和文字总宽度 计算出一共可以画几行
int rowCount = (mTextTotalWidth / viewWidth) + 1;//需要画的行数
System.out.println("rowCount:" + rowCount);*/
//对所有的文字进行计算宽度并拆分成行,逐个取出文字计算宽度,如果到达view的宽度 则为一行,然后开始下一行
char[] mTextChars = mText.toCharArray();
StringBuilder stringBuilder = new StringBuilder();
//一行字符的宽度
float textWidth = 0;
//保存数据行的集合
rowTextList = new ArrayList<>();
//循环取出字符
for (int i = 0; i < mTextChars.length; i++) {
char c = mTextChars[i];
//把字符宽度相加
textWidth = textWidth + mPaint.measureText(c + "");
//把相加后的字符 放到stringBuilder中
stringBuilder.append(c);
//判断当前保存到stringBuilder中的 字符串是否超过了 view自身的宽度,
// 如果超过了就把前sb中的字符保存起来,(这个保存的为一行文字数据)
//保存一行后 重新开始计算下一行
if (textWidth >= viewWidth) {
//已够一行数据 保存到集合中
rowTextList.add(stringBuilder.toString());
//清空记录数据 重新开始计算其他字符 拼装新的行数据
stringBuilder.delete(0, stringBuilder.length());
textWidth = 0;
}
//判断是否循环到了最后一个字符
if (i == mTextChars.length - 1) {
//此时为最后一行
rowTextList.add(stringBuilder.toString());
}
}
System.out.println("----rowTextList:" + rowTextList.get(0));
System.out.println("----rowTextList:size " + rowTextList.size());
}
public int getDirection() {
return mDirection;
}
public void setDirection(int mDirection) {
this.mDirection = mDirection;
}
public float getMProgress() {
return mProgress;
}
public void setMProgress(int mProgress) {
if(mProgress == 0){
//清空数据重新画
clearText();
}
if (this.mProgress != mProgress) {
this.mProgress = mProgress;
System.out.println("---------mProgress:" + mProgress);
invalidate();
}
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
attr.xml (自定义属性文件 位置 res -> values ->attr.xml)
<resources>
<attr name="text" format="string"></attr>
<attr name="text_size" format="dimension"></attr>
<attr name="text_origin_color" format="color|reference"></attr>
<attr name="text_change_color" format="color|reference"></attr>
<attr name="progress" format="float"></attr>
<attr name="row_height" format="integer"></attr>
<attr name="direction">
<enum name="left" value="0"></enum>
<enum name="right" value="1"></enum>
</attr>
<declare-styleable name="ColorTrackView">
<attr name="text"></attr>
<attr name="text_size"></attr>
<attr name="text_origin_color"></attr>
<attr name="text_change_color"></attr>
<attr name="progress"></attr>
<attr name="direction"></attr>
<attr name="row_height"></attr>
</declare-styleable>
</resources>
布局中引用:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="@android:color/darker_gray"
tools:context=".activity.MainActivity">
<com.eway.lib_audio.view.ColorTrackView
android:id="@+id/show_voice_text2"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:text="请您在画板上,画出您看到的图形,一笔只画一条线,不能折。请您在画板上,画出您看到的图形,一笔只画一条线,不能折。"
app:text_size="14sp"//字体大小
app:row_height="30"//这个属性是 绘制文字的每行的高度
app:text_change_color="#08A7FD" //这个是滚动时的颜色
app:text_origin_color="@color/white"/>//这个时背景默认的颜色
</androidx.constraintlayout.widget.ConstraintLayout>
代码中使用:
@Override
protected void onClickImpl(View view) {
switch (view.getId()){
case R.id.show_voice_text2:
ObjectAnimator.ofInt(colorTrackView, "mProgress", 0,100).setDuration(5000).start();
break;
}
这里说明一下,使用原理就是 调用ColorTrackView自定义控件中的 setMProgress(percent); 方法。
percent值传0-100最好,可根据自己需求改。
我是这么用的,讯飞语音在阅读文字时 会把当前的阅读 进度反馈给我 值时0-100
//这是讯飞语音一个回调方法
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
// 播放进度 percent值为0-100
colorTrackView.setMProgress(percent);
}
总结:
1.自定义ColorTrackView(复制粘贴过去即可)
2.在layout布局中引用 设置文字 文字大小 行高 滚动颜色 文字初始颜色
3.在代码中分次调用 colorTrackView.setMProgress(percent); 最好是 0-100次调用
着急干活,写的不是很详细。