Android 歌词滚动效果(歌词逐个与逐渐变色)可换行

前言:

最近公司要求实现一个  讯飞语音阅读文字,文字根据阅读速度逐个变色的功能。先上个图看下效果。

(由于工作非常紧张,所以就把测试的图贴过来了,兄弟们将就看)

直接上代码:

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次调用

着急干活,写的不是很详细。

猜你喜欢

转载自blog.csdn.net/gaoqingliang521/article/details/115693960