Android实现文字动态高亮读取进度效果

本文实例为大家分享了Android实现文字动态高亮读取进度的具体代码,供大家参考,具体内容如下

1、效果图

类似歌词的效果。播放下面文字的音频,同时音频播放的进度和文字高亮进度保持一致。

2、代码结构和实现

简单的类图:

ISubtitleView接口代码如下:

/**
 * 简要功能描述
 * <p>
 * <详细功能描述>
 *
 * @author : liuxs
 * @date : 2021/3/18
 */
public interface ISubtitleView {

    /**
     * 获取当前的带时间文字歌词实体类
     * @return
     */
    List<SubtitleItem> getSubtitleItemList();

    /**
     * 设置带时间文字歌词实体类
     * @param linesTextList
     */
    void setSubtitleItemList(List<SubtitleItem> linesTextList);

    /**
     * just like {@link #setSubtitleItemList(List)} , only call one of them
     * @param duration
     */
    void setDuration(long duration);

    /**
     * 更新pts会刷新文字进度
     * @param pts
     */
    void updatePts(long pts);

    /**
     * 复位
     */
    void reset();
}
EMSubtitleView类的代码如下:

/**
 * 歌词文字效果的基础view
 * <p>
 * <详细功能描述>
 *
 * @author : liuxs
 * @date : 2021/3/17
 */
public class EMSubtitleView extends android.support.v7.widget.AppCompatTextView  implements ISubtitleView{

    private int mMeasuredWidth;
    private int mMeasuredHeight;
    private List<SubtitleItem> mSubtitleItemList;
    private List<LineEntity> mLinesTextList;
    private Paint mNormalPaint;
    private Paint mHLPaint;
    private long mPts = 0;
    private int mCurHLLine;
    private float mReadSubtitleCount;
    private Rect mLastHLRect;
    private int mHLTextColor;
    private long mDuration;
    private boolean mIsPlain = false;

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

    public EMSubtitleView(Context context, AttributeSet attrs) {
        this(context, attrs , 0);
    }

    public EMSubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mSubtitleItemList = new ArrayList<>();
        mLinesTextList = null;
        mLastHLRect = new Rect();
        mNormalPaint = null;
        initAttrs(attrs , defStyleAttr);

    }
    private void initAttrs(AttributeSet attrs , int defStyleAttr) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs , R.styleable.EMSubtitleView , defStyleAttr , R.style.EMSubtitleViewDefaultTheme);
        for(int i = 0 ; i < ta.getIndexCount() ; i++){
            int index = ta.getIndex(i);
            if (index == R.styleable.EMSubtitleView_highLightTextColor) {
                mHLTextColor = ta.getColor(index, getResources().getColor(R.color.emvideovisit_color_FF9000));
            }
        }
        ta.recycle();
    }
    private void initHLPaint() {
        mHLPaint = new Paint(mNormalPaint);
        mHLPaint.setColor(mHLTextColor);
        mHLPaint.setTextSize(getTextSize());
    }

    /**
     * 设置文字
     * 作为可以滚动高亮显示的文本
     * @param text
     */
    public void setRichText(String text){
        mIsPlain = false;
        reset();
        setText(text);
        setDuration(mDuration , true);
    }

    /**
     * 设置文字
     * 当作普通的textView使用
     * @param text
     */
    public void setPlainText(String text){
        mIsPlain = true;
        reset();
        setText(text);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mMeasuredWidth == 0 || mMeasuredHeight == 0) {
            mMeasuredWidth = getMeasuredWidth();
            mMeasuredHeight = getMeasuredHeight();
        }

        if(mIsPlain){
            super.onDraw(canvas);
            return;
        }
        if(mNormalPaint == null){
            super.onDraw(canvas);
            mNormalPaint = getPaint();
            initHLPaint();
            return;
        }

        if(mLinesTextList == null){
            fillLinesEntityListJustOnce();
        }
        //没有pts,绘制
        if(mPts <= 0){
            drawNormalText(canvas);
            return;
        }
        drawNormalText(canvas);
        calculateReadLineAndWordsCount();

        //绘制高亮部分歌词
        drawHLText(canvas);
    }

    private void drawHLText(Canvas canvas) {
        if(mCurHLLine >= 0){
            int curLineTextCount = 0;
            for(int i = 0 ; i < mLinesTextList.size() ; ++i){
                LineEntity entity = mLinesTextList.get(i);
                if(mCurHLLine > i){
                    canvas.drawText( entity.lineText , entity.left , entity.baseLine , mHLPaint);
                }else if(mCurHLLine == i && mReadSubtitleCount > 0 ){
                    canvas.save();
                    mLastHLRect.set(entity.left , entity.top , entity.left + (int) (mHLPaint.measureText(entity.lineText)*1.0f/entity.lineText.length()*(mReadSubtitleCount-curLineTextCount)), entity.bottom);
                    canvas.clipRect(mLastHLRect );
                    canvas.drawText( entity.lineText , entity.left , entity.baseLine , mHLPaint);
                    canvas.restore();
                    //遮挡的话需要滚动
                    if(entity.baseLine > getHeight()){
                        if(getScrollY() != entity.bottom - getHeight() + 1){
                            setScrollY( entity.bottom - getHeight() + 1/*- pts/40000 * mMeasuredHeight*/);
                        }
                    }
                    break;
                }
                curLineTextCount += entity.lineText.length();
            }
        }
    }

    private void calculateReadLineAndWordsCount() {
        float curSubtitleCount = 0;
        for(SubtitleItem subtitleItem :  mSubtitleItemList){
            //文字之间不可以有空隙,否则mCurHLLine可能一直为-1;
            if(mPts >= subtitleItem.getStartTime() && mPts < subtitleItem.getEndTime()){
                float lineOffset = (subtitleItem.getWords().length()*1.0f/(subtitleItem.getEndTime() - subtitleItem.getStartTime()))*(mPts-subtitleItem.getStartTime());
                curSubtitleCount += lineOffset;
                int curLineTextCount = 0;
                for(int i = 0 ; i < mLinesTextList.size() ; ++i){
                    curLineTextCount += mLinesTextList.get(i).lineText.length();
                    if(curLineTextCount > curSubtitleCount){
                        mCurHLLine = i;
                        break;
                    }
                }
                break;
            }
            curSubtitleCount += subtitleItem.getWords().length();
        }
        mReadSubtitleCount = curSubtitleCount;
    }

    private void fillLinesEntityListJustOnce() {
        if(mLinesTextList != null){
            return;
        }
        mLinesTextList = new ArrayList<>();
        Layout layout = getLayout();
        int line=getLayout().getLineCount();
        String text=layout.getText().toString();
        for(int i=0;i<line;i++){
            int start=layout.getLineStart(i);
            int end=layout.getLineEnd(i);
            int left = (int) layout.getLineLeft(i);
            int baseLine = layout.getLineBaseline(i);
            Paint.FontMetrics fontMetrics = getPaint().getFontMetrics();
            int top = (int) (baseLine + fontMetrics.top);
            int bottom = (int) (baseLine + fontMetrics.bottom);
            int ascent = (int) (baseLine + fontMetrics.ascent);
            int descent = (int) (baseLine + fontMetrics.descent);

            mLinesTextList.add(new LineEntity(text.substring(start, end) , top , bottom , ascent , descent ,baseLine , left));
        }
    }

    private void drawNormalText(Canvas canvas) {
        for(LineEntity entity : mLinesTextList){
            canvas.drawText(entity.lineText, entity.left,  entity.baseLine , mNormalPaint);
        }
    }

    public List<SubtitleItem> getSubtitleItemList() {
        return mSubtitleItemList;
    }

    /**
     * 设置带时间文字歌词实体类
     * @param linesTextList
     */
    public void setSubtitleItemList(List<SubtitleItem> linesTextList) {
        if(mSubtitleItemList != null){
            mSubtitleItemList.clear();
            mSubtitleItemList.addAll(linesTextList);
        }else{
            this.mSubtitleItemList = linesTextList;
        }
    }

    /**
     * must first call setText,then call here
     * @param duration
     */
    public void setDuration(long duration){
        setDuration( duration , false);
    }

    private void setDuration(long duration , boolean force){
        if((duration > 0 && mDuration != duration) || force){
            mDuration = duration;
            if(mSubtitleItemList != null){
                mSubtitleItemList.clear();
            }else{
                this.mSubtitleItemList = new ArrayList<>();
            }
            mSubtitleItemList.add(new SubtitleItem(getText().toString() , 0 , duration));
        }
    }

    /**
     * 更新pts会刷新文字进度
     * @param pts
     */
    public void updatePts(long pts){
        mPts = pts;
        postInvalidate();
    }

    /**
     * 复位
     */
    public void reset(){
        mPts = 0;
        mCurHLLine = 0;
        mReadSubtitleCount = 0;
        mLinesTextList = null;
        mNormalPaint = null;
        setScrollY(0);
        postInvalidate();

    }

    static class LineEntity{
       String lineText;
       int top;
       int bottom;
       int ascent;
       int descent;
       int baseLine;
       int left;

        public LineEntity(String lineText, int top, int bottom, int ascent, int descent, int baseLine, int left) {
            this.lineText = lineText;
            this.top = top;
            this.bottom = bottom;
            this.ascent = ascent;
            this.descent = descent;
            this.baseLine = baseLine;
            this.left = left;
        }

    }
}

布局文件里使用类似如下:

<com.eastmoney.emvideovisit.view.EMSubtitleView
        android:id="@+id/play_text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/emvideovisit_dp_60"
        android:layout_marginLeft="@dimen/emvideovisit_dp_60"
        android:layout_marginRight="@dimen/emvideovisit_dp_60"
        android:gravity="center_horizontal"
        android:layout_marginTop="@dimen/emvideovisit_dp_20"
        android:textColor="#fff"
        app:highLightTextColor="#A6EA5504"
        android:text="@string/emvideovisit_string_headset_tips"
        android:textSize="@dimen/emvideovisit_dp_16"
        android:layout_marginBottom="@dimen/emvideovisit_dp_4"/>

3、其它

处理过程中文字位置的参数需要注意:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android progressbar实现带底部指示器和文字的进度条

    本文实例为大家分享了Android实现带指示器和文字的进度条,供大家参考,具体内容如下 根据项目要求需要实现以下效果: 列出源码: public class TextProgressBar extends LinearLayout { String text; Paint mPaint; private Rect textRect; private Bitmap bitmap; private ProgressBar progressBar; int progress; int proWidth

  • Android view自定义带文字带进度的控件

    目标:自定义一个带文字带进度的控件,具体内容如下 效果图: 不啰嗦先看东西: 步骤分析 提取自定义属性 //提供对外暴露的属性,如有不够自己扩展 <declare-styleable name="DescProgressView"> <attr name="dpv_text_normal_color" format="color" /> <attr name="dpv_text_seleced_color&

  • Android实现文字动态高亮读取进度效果

    本文实例为大家分享了Android实现文字动态高亮读取进度的具体代码,供大家参考,具体内容如下 1.效果图 类似歌词的效果.播放下面文字的音频,同时音频播放的进度和文字高亮进度保持一致. 2.代码结构和实现 简单的类图: ISubtitleView接口代码如下: /** * 简要功能描述 * <p> * <详细功能描述> * * @author : liuxs * @date : 2021/3/18 */ public interface ISubtitleView { /** *

  • Android 自定义球型水波纹带圆弧进度效果(实例代码)

    需求 如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度. 思路 外围圆弧进度:可以通过canvas.drawArc()实现.由于圆弧需要实现渐变,可以通过给画笔设置shader(SweepGradient)渲染,为了保证圆弧起始的颜色值始终一致,需要动态调整shader的参数.具体参见 SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value

  • Android自定义带动画的半圆环型进度效果

    本文实例为大家分享了Android半圆环型进度效果的具体代码,供大家参考,具体内容如下 package com.newair.ondrawtext; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Can

  • Android实现文字翻转动画的效果

    本文实现了Android程序文字翻转动画的小程序,具体代码如下: 先上效果图如下: 要求: 沿Y轴正方向看,数值减1时动画逆时针旋转,数值加1时动画顺时针旋转. 实现动画的具体细节见"RotateAnimation.Java".为方便查看动画旋转方向,可以将RotateAnimation.DEBUG值设置为true即可.
 RotateAnimation参考自APIDemos的Rotate3DAnimation
 RotateAnimation的构造函数需有三个参数,分别说明动画组件的

  • Android编程使用自定义View实现水波进度效果示例

    本文实例讲述了Android编程使用自定义View实现水波进度效果.分享给大家供大家参考,具体如下: 首先上效果图: 简介: 1.自动适应屏幕大小: 2.水波自动横向滚动: 3.各种绘制参数可通过修改常量进行控制. 代码不多,注释也比较详细,全部贴上: (一)自定义组件: /** * 水波进度效果. */ public class WaterWaveView extends View { //边框宽度 private int STROKE_WIDTH; //组件的宽,高 private int

  • Android实现文字垂直滚动、纵向走马灯效果的实现方式汇总

    方法一.使用系统控件ViewFlipper方式: 布局文件: <ViewFlipper android:id="@+id/view_flipper" android:layout_width="300dp" android:layout_height="35dp" android:layout_centerInParent="true" android:autoStart="true" android

  • Android实现标题上显示隐藏进度条效果

    一个界面,实现在向页面添加图片时,在标题上显示一个水平进度条,当图片载入完毕后,隐藏进度条并显示图片 具体实现方法: res/layout/main.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"

  • Android 使用 Path 实现搜索动态加载动画效果

    今天实现一个搜索动态加载数据的动画效果,还是先看效果吧,用文字描述干巴巴的,看图说话什么都明白了, 实现这个就是使用Path中的getSegment()不断的去改变它截取片段的start和stop,再结合动画,今天就分步骤实现它,看完以后你也会觉的不是很难,只是没想到这么实现而已,所以要多见识,所谓眼界决定你的高度,还是延续我写博客的习惯,一步步分析,第一步就是绘制如下图: 如果单纯的绘制这个图很简单很简单的,绘制一个圆,然后再绘制一根线就搞定,但是要考虑这里的效果,就不能这么干了,如果你看了上

  • Android实现圆线按钮进度效果

    本文实例为大家分享了Android实现圆线按钮进度效果的具体代码,供大家参考,具体内容如下 先看效果图: 这是一个在github上的开源控件按钮View(点击此处查看),同时带有进度. 使用方法:把该项目从github上下载下来导入到eclipse,然后作为库,接下来在其他项目中直接引用即可.然而,我感觉原生项目中的个别细节代码不是太完善,我在它的MasterLayout.java类增加了一些字段和方法: // 增加的值,by Phil public static final int START

  • Android动态自定义圆形进度条

    效果图: A.绘制圆环,圆弧,文本 //1.画圆环 //原点坐标 float circleX = width / 2; float circleY = width / 2; //半径 float radius = width / 2 - roundWidth / 2; //设置画笔的属性 paint.setColor(roundColor); paint.setStrokeWidth(roundWidth); paint.setStyle(Paint.Style.STROKE); canvas.

随机推荐