Android實(shí)現(xiàn)文字動(dòng)態(tài)高亮讀取進(jìn)度效果
本文實(shí)例為大家分享了Android實(shí)現(xiàn)文字動(dòng)態(tài)高亮讀取進(jìn)度的具體代碼,供大家參考,具體內(nèi)容如下
1、效果圖
類似歌詞的效果。播放下面文字的音頻,同時(shí)音頻播放的進(jìn)度和文字高亮進(jìn)度保持一致。

2、代碼結(jié)構(gòu)和實(shí)現(xiàn)
簡(jiǎn)單的類圖:

ISubtitleView接口代碼如下:
/**
* 簡(jiǎn)要功能描述
* <p>
* <詳細(xì)功能描述>
*
* @author : liuxs
* @date : 2021/3/18
*/
public interface ISubtitleView {
/**
* 獲取當(dāng)前的帶時(shí)間文字歌詞實(shí)體類
* @return
*/
List<SubtitleItem> getSubtitleItemList();
/**
* 設(shè)置帶時(shí)間文字歌詞實(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會(huì)刷新文字進(jìn)度
* @param pts
*/
void updatePts(long pts);
/**
* 復(fù)位
*/
void reset();
}
EMSubtitleView類的代碼如下:
/**
* 歌詞文字效果的基礎(chǔ)view
* <p>
* <詳細(xì)功能描述>
*
* @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è)置文字
* 作為可以滾動(dòng)高亮顯示的文本
* @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();
//遮擋的話需要滾動(dòng)
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è)置帶時(shí)間文字歌詞實(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會(huì)刷新文字進(jìn)度
* @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ù)需要注意:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android8.1原生系統(tǒng)網(wǎng)絡(luò)感嘆號(hào)消除的方法
- Android開發(fā)實(shí)現(xiàn)消除屏幕鎖的方法
- Android 實(shí)現(xiàn)文字左右對(duì)齊
- Android基于AdapterViewFlipper實(shí)現(xiàn)的圖片/文字輪播動(dòng)畫控件
- Android獲取文字高度的三種方法
- Android Button按鈕點(diǎn)擊背景和文字變化操作
- Android實(shí)現(xiàn)文字滾動(dòng)播放效果的代碼
- Android實(shí)現(xiàn)文字下方加橫線
- android命令行模擬輸入事件(文字、按鍵、觸摸等)
- Android實(shí)現(xiàn)文字消除效果
相關(guān)文章
Android模仿實(shí)現(xiàn)微博詳情頁滑動(dòng)固定頂部欄的效果實(shí)例
這篇文章主要給大家介紹了關(guān)于利用Android模仿實(shí)現(xiàn)微博詳情頁滑動(dòng)固定頂部欄效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11
常見的8個(gè)Android內(nèi)存泄漏問題及解決方法
在Android開發(fā)中,內(nèi)存泄漏是一個(gè)常見的問題,這個(gè)問題可能會(huì)導(dǎo)致應(yīng)用程序變慢、崩潰或者消耗大量的內(nèi)存,最終導(dǎo)致設(shè)備性能下降,本文就給大家總結(jié)一下最常見的8個(gè)Android內(nèi)存泄漏問題及解決方法,需要的朋友可以參考下2023-07-07
Android編程中沉浸式狀態(tài)欄的三種實(shí)現(xiàn)方式詳解
這篇文章主要介紹了Android編程中沉浸式狀態(tài)欄的三種實(shí)現(xiàn)方式,簡(jiǎn)單描述了沉浸式狀態(tài)欄的概念、功能并結(jié)合實(shí)例形式詳細(xì)分析了Android實(shí)現(xiàn)沉浸式狀態(tài)欄的三種操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-02-02
Android?雙屏異顯自適應(yīng)Dialog的實(shí)現(xiàn)
Android 多屏互聯(lián)的時(shí)代,必然會(huì)出現(xiàn)多屏連接的問題,本文主要介紹了Android?雙屏異顯自適應(yīng)Dialog的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
Android更多條目收縮展開控件ExpandView的示例代碼
本篇文章主要介紹了Android更多條目收縮展開控件ExpandView的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01
Android實(shí)現(xiàn)引導(dǎo)頁的圓點(diǎn)指示器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)引導(dǎo)頁的圓點(diǎn)指示器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06

