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

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

1、效果图

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

6690ceb2153c280ea97c3d8c83c8a6f9.png

2、代码结构和实现

简单的类图:

53869453479d22eeaee65c14da813fc7.png

ISubtitleView接口代码如下:

/**

* 简要功能描述

*

*

*

* @author : liuxs

* @date : 2021/3/18

*/

public interface ISubtitleView {

/**

* 获取当前的带时间文字歌词实体类

* @return

*/

List getSubtitleItemList();

/**

* 设置带时间文字歌词实体类

* @param linesTextList

*/

void setSubtitleItemList(List 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

*

*

*

* @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 mSubtitleItemList;

private List 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

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 getSubtitleItemList() {

return mSubtitleItemList;

}

/**

* 设置带时间文字歌词实体类

* @param linesTextList

*/

public void setSubtitleItemList(List 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;

}

}

}

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

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、其它

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

1f588a4cd2173cdb301271121a8543b4.png

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值