Android實現(xiàn)文字動態(tài)高亮讀取進度效果
本文實例為大家分享了Android實現(xiàn)文字動態(tài)高亮讀取進度的具體代碼,供大家參考,具體內(nèi)容如下
1、效果圖
類似歌詞的效果。播放下面文字的音頻,同時音頻播放的進度和文字高亮進度保持一致。
2、代碼結(jié)構(gòu)和實現(xiàn)
簡單的類圖:
ISubtitleView接口代碼如下:
/** * 簡要功能描述 * <p> * <詳細功能描述> * * @author : liuxs * @date : 2021/3/18 */ public interface ISubtitleView { /** * 獲取當(dāng)前的帶時間文字歌詞實體類 * @return */ List<SubtitleItem> getSubtitleItemList(); /** * 設(shè)置帶時間文字歌詞實體類 * @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); /** * 復(fù)位 */ void reset(); } EMSubtitleView類的代碼如下: /** * 歌詞文字效果的基礎(chǔ)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()); } /** * 設(shè)置文字 * 作為可以滾動高亮顯示的文本 * @param text */ public void setRichText(String text){ mIsPlain = false; reset(); setText(text); setDuration(mDuration , true); } /** * 設(shè)置文字 * 當(dāng)作普通的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; } /** * 設(shè)置帶時間文字歌詞實體類 * @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(); } /** * 復(fù)位 */ 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、其它
處理過程中文字位置的參數(shù)需要注意:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android即時通訊設(shè)計(騰訊IM接入和WebSocket接入)
本文主要介紹了Android即時通訊設(shè)計(騰訊IM接入和WebSocket接入),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Android游戲開發(fā) 自定義手勢--輸入法手勢技術(shù)
本文主要介紹 Android游戲開發(fā)中自定義手勢--輸入法手勢技術(shù),這里提供了實現(xiàn)效果圖以及示例代碼,有開發(fā)手機游戲的朋友可以參考下2016-08-08Android中Toolbar隨著ScrollView滑動透明度漸變效果實現(xiàn)
這篇文章主要介紹了Android中Toolbar隨著ScrollView滑動透明度漸變效果實現(xiàn),非常不錯,具有參考借鑒價值,需要的的朋友參考下2017-01-01Android實現(xiàn)炫酷的網(wǎng)絡(luò)直播彈幕功能
這篇文章主要為大家詳細介紹了Android仿網(wǎng)絡(luò)直播彈幕功能的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android Studio 4.0 正式發(fā)布在Ubuntu 20.04中安裝的方法
這篇文章主要介紹了Android Studio 4.0 正式發(fā)布如何在Ubuntu 20.04中安裝,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Android仿微信菜單(Menu)(使用C#和Java分別實現(xiàn))
這篇文章主要介紹了Android仿微信菜單(Menu)(使用C#和Java分別實現(xiàn)),本文分別給出C#和Java版的運行效果及實現(xiàn)代碼,需要的朋友可以參考下2015-06-06Android獲取應(yīng)用程序名稱(ApplicationName)示例
本文以實例方式為大家介紹下獲取應(yīng)用程序名稱(ApplicationName)的具體實現(xiàn),感興趣的各位可以參考下哈2013-06-06