因?yàn)榍岸螘r(shí)間在做廣告機(jī),正好需要用到字幕滾動,期間也踩了一些坑,所以這邊就講一下安卓字幕滾動的幾種實(shí)現(xiàn)方案。(這邊只講水平滾動,不講垂直滾動,因?yàn)樵硎穷愃频模?/p>
1.最簡單的實(shí)現(xiàn)方案
首先,我們可以直接使用系統(tǒng)的 TextView 控件。通過查詢系統(tǒng)源碼,我們可以發(fā)現(xiàn),在TextView控件里面有個(gè)Marquee.class 內(nèi)部類,而這個(gè)類又是控制 TextView 文本滾動的。所以我們可以這樣添加一個(gè)TextView控件
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginBottom="8dp"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true" />
或
TextView textView = new TextView(context);
textView.setSingleLine();
textView.setMarqueeRepeatLimit(-1);//循環(huán)次數(shù),-1無限循環(huán)
同時(shí),我們根據(jù)源碼要求,必須滿足 isFocused() || isSelected() ,所以當(dāng)需要循環(huán)的時(shí)候,可以調(diào)用 textView.setSelected(true);
2.稍微復(fù)雜的方案
如果需要對文本滾動速度進(jìn)行調(diào)節(jié)的,那么使用TextView 的限制就比較大了,當(dāng)然你也可以通過使用hook或者修改源碼等一系列方案來修改TextView,不過那太麻煩了,除非你愿意。所以,我們可以考慮通過定制一個(gè)View ,然后設(shè)定畫筆畫布,繪制指定的文本,再做個(gè)定時(shí)器,讓文本滾動起來。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
?
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
?
public class ScrollTextView1 extends SurfaceView implements SurfaceHolder.Callback {
private final String TAG = "ScrollTextView";
// surface Handle onto a raw buffer that is being managed by the screen compositor.
private SurfaceHolder surfaceHolder; //providing access and control over this SurfaceView's underlying surface.
?
private Paint paint = null;
private boolean stopScroll = false; // stop scroll
private boolean pauseScroll = false; // pause scroll
private int speed = 4; // scroll-speed
private int textColor = Color.BLACK;
private String text = ""; // scroll text
private float textSize = 20f; // default text size
?
private int viewWidth = 0;
private int viewHeight = 0;
private float textWidth = 0f;
private float textX = 0f;
private float textY = 0f;
private float viewWidth_plus_textLength = 0.0f;
?
private ScheduledExecutorService scheduledExecutorService;
?
boolean isSetNewText = false;
?
/**
* constructs 1
*
* @param context you should know
*/
public ScrollTextView1(Context context) {
super(context);
}
?
/**
* constructs 2
*
* @param context CONTEXT
* @param attrs ATTRS
*/
public ScrollTextView1(Context context, AttributeSet attrs) {
super(context, attrs);
surfaceHolder = this.getHolder(); //get The surface holder
surfaceHolder.addCallback(this);
paint = new Paint();
?
paint.setColor(textColor);
paint.setTextSize(textSize);
?
setZOrderOnTop(true); //Control whether the surface view's surface is placed on top of its window.
getHolder().setFormat(PixelFormat.TRANSLUCENT);
?
setFocusable(true);
}
?
/**
* measure text height width
*
* @param widthMeasureSpec widthMeasureSpec
* @param heightMeasureSpec heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
?
int mHeight = getFontHeight(textSize); //實(shí)際的視圖高
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
?
// when layout width or height is wrap_content ,should init ScrollTextView Width/Height
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(viewWidth, mHeight);
viewHeight = mHeight;
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(viewWidth, viewHeight);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(viewWidth, mHeight);
viewHeight = mHeight;
}
}
?
?
/**
* surfaceChanged
*
* @param arg0 arg0
* @param arg1 arg1
* @param arg2 arg1
* @param arg3 arg1
*/
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
Log.d(TAG, "arg0:" + arg0.toString() + " arg1:" + arg1 + " arg2:" + arg2 + " arg3:" + arg3);
}
?
/**
* surfaceCreated,init a new scroll thread.
* lockCanvas
* Draw something
* unlockCanvasAndPost
*
* @param holder holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
stopScroll = false;
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(new ScrollTextThread(), 100, 100, TimeUnit.MILLISECONDS);
Log.d(TAG, "ScrollTextTextView is created");
}
?
/**
* surfaceDestroyed
*
* @param arg0 SurfaceHolder
*/
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
stopScroll = true;
scheduledExecutorService.shutdownNow();
Log.d(TAG, "ScrollTextTextView is destroyed");
}
?
?
/**
* text height
*
* @param fontSize fontSize
* @return fontSize`s height
*/
private int getFontHeight(float fontSize) {
// Paint paint = new Paint();
paint.setTextSize(fontSize);
FontMetrics fm = paint.getFontMetrics();
return (int) Math.ceil(fm.descent - fm.ascent);
}
?
/**
* set scroll text
*
* @param newText scroll text
*/
public void setText(String newText) {
isSetNewText = true;
stopScroll = false;
this.text = newText;
measureVarious();
}
?
/**
* Draw text
*
* @param X X
* @param Y Y
*/
private synchronized void draw(float X, float Y) {
Canvas canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
canvas.drawText(text, X, Y, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
?
/**
* measure text
*/
private void measureVarious() {
textWidth = paint.measureText(text);
viewWidth_plus_textLength = viewWidth + textWidth;
textX = viewWidth - viewWidth / 5;
?
//baseline measure !
FontMetrics fontMetrics = paint.getFontMetrics();
float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
textY = viewHeight / 2 + distance;
}
?
?
/**
* Scroll thread
*/
class ScrollTextThread implements Runnable {
@Override
public void run() {
measureVarious();
while (!stopScroll) {
if (pauseScroll) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Log.e(TAG, e.toString());
}
continue;
}
draw(viewWidth - textX, textY);
textX += speed;
if (textX > viewWidth_plus_textLength) {
textX = 0;
}
}
}
}
}
參考demo 大佬鏈接
3.有點(diǎn)麻煩的方案
通過以上方案,大概可以解決百分之85的水平字幕滾動需求了。當(dāng)然,還有各種特殊情況,比如部分主板繪制超過千字的文本,會出現(xiàn)左起點(diǎn)文字重疊的現(xiàn)象,

通過多次試驗(yàn),發(fā)現(xiàn)這些主板對應(yīng)的系統(tǒng)必須保證 Canves.drawText() 函數(shù)每次繪制的文字不能超過指定的字?jǐn)?shù)(大部分是千字)。那么,針對這一原則,為了保證每次繪制的文字不能超過千字,所以需要對文字進(jìn)行裁切。那么這里就出現(xiàn)了一個(gè)問題。我們只要一個(gè)字幕滾動,切成多份的文字之后,要保證每段文字可以上下銜接同步滾動,就必須對文字的寬高進(jìn)行計(jì)算。
這里擴(kuò)展延伸一下安卓字符寬高的計(jì)算方案:
首先,我們需要知道每個(gè)字符的計(jì)算規(guī)則如下圖所示

于是,我們就有了以下計(jì)算文字寬度的方案:
TextPaint paint = new TextPaint();
paint.setTextAlign(Paint.Align.LEFT);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
?
String str = "test msg ... 1k";
?
// 第一種 較精確
float len1 = Layout.getDesiredWidth(str, 0, str.length(), paint);
// 第二種 取近似值,較大
float len2 = 0;
float widths[] = new float[str.length()];
mPaint.getTextWidths(str, widths);
for (int i = 0; i < len; i++) {
len2 += Math.ceil(widths[i]);
}
// 第三種 較精確,在某種情況下與第一種相同
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
float len3 = rect.width();
// 第四種 取近似值
float len4 = paint.measureText(cacheStr);
//第五種 更精確,更小
Path textPath = new Path();
RectF boundsPath = new RectF();
paint.getTextPath(str, 0, str.length(), 0.0f, 0.0f, textPath);
textPath.computeBounds(boundsPath, true);
float len5 = boundsPath.width();
同時(shí),經(jīng)過多種主板測試,我們發(fā)現(xiàn)測量文字寬高必須是屏幕可容納字?jǐn)?shù)之內(nèi)才是較為精確的。比如屏幕最多容納100個(gè)字的情況下,測量100個(gè)字符的寬高是準(zhǔn)確的,但是測量超過這個(gè)數(shù)量之后,可能出現(xiàn)長度偏大或偏小的情況。
考慮到我們把字符串分成n段,而每一段的長度可能不一樣,假設(shè)每段都接近一個(gè)屏幕寬,然后給每段設(shè)定一個(gè)x坐標(biāo),那么最少需要三段字幕。當(dāng)滾動第一段完畢的時(shí)候,第二段完整展示在界面上面,第三段可能顯示出來(即第一段的x為負(fù)的第一段寬度,第二段的x為0,第三段的x為第二段的寬)。于是,我們就可以構(gòu)造這樣的控件。
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.text.Html;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.LinearLayout;
?
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Locale;
?
?
public class MarqueeTextView extends SurfaceView implements SurfaceHolder.Callback {
?
private static final String TAG = "打印_MarqueeTextView";
?
public static final int MAX_SPEED = 50;
public static final int MIN_SPEED = 0;
?
private Paint paint = null;// 畫筆
?
private float textSize = 15f; // 字號
private int textColor = Color.BLACK; // 文字顏色
private int bgColor = Color.GRAY; // 背景顏色
?
private int orizontal = LinearLayout.HORIZONTAL; // HORIZONTAL 水平滾動|VERTICAL 垂直滾動
private float speed = 4; // 滾動速度
private SurfaceHolder holder;
?
?
// 按每屏長的文字,緩存到列表
private final LinkedList<MarqueeBean> txtCacheList = new LinkedList<>();
private String oldStr = "";//緩存文字,作為比對使用
?
private int mTextDistance = 80;//item間距,dp單位
?
private Thread mScheduledThread; //滾動線程
?
private float mLoc_X_1 = 0;//第一屏的x坐標(biāo)
private float mLoc_Y_1 = 0;//第一屏的y坐標(biāo)
?
private float offsetDis = 0;//偏移量,以速度為基準(zhǔn)
?
private int mIndex_1 = 0;//第一屏的文字角標(biāo)
private int mIndex_2 = 1;//第二屏的文字角標(biāo)
private int mIndex_3 = 2;//第三屏的文字角標(biāo)
?
private boolean isRolling = false;//是否在滾動
private boolean isInterrupted = true;//是否停止線程循環(huán)
?
private float totalLength = 0.0f;// 顯示總長度
private int totalTimes = -1; // 滾動次數(shù)
private int doneCount = 0;//準(zhǔn)備執(zhí)行滾動的次數(shù)
?
private float simpleHeight; //單文字高度
?
?
public MarqueeTextView(Context context) {
this(context, null);
}
?
public MarqueeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
?
public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
?
private void init(AttributeSet attrs) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(textColor);
paint.setTextSize(textSize);
?
setSpeed(speed);
setText(null);
}
?
// 獲取字體寬
private float getFontWith(String txt) {
return paint.measureText(txt);
}
?
?
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
?
if (holder == null) {
holder = getHolder();
holder.removeCallback(this);
holder.addCallback(this);
}
?
}
?
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility != View.VISIBLE) {
stopRolling();
} else {
startRolling();
}
}
?
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
?
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
?
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopRolling();
}
?
private Rect rect;
?
private float getContentWidth(String black) {
if (black == null || black.length() == 0) {
return 0;
}
if (rect == null) {
rect = new Rect();
}
paint.getTextBounds(black, 0, black.length(), rect);
return rect.width();
}
?
private float getBlackWidth() {
String text1 = "en en";
String text2 = "enen";
return getContentWidth(text1) - getContentWidth(text2);
}
?
/**
* 重置參數(shù)
*/
private void reset() {
if (txtCacheList.size() <= 0) return;
?
mIndex_1 = 0;//第一屏的文字角標(biāo)
simpleHeight = FormatTextTask.getFontHeight(textSize);
?
//fixme 這邊先不考慮內(nèi)邊距
// 水平滾動
totalLength = getWidth();
?
//定高
mLoc_Y_1 = getHeight() / 2 + simpleHeight / 3;
?
paint.setTextAlign(Paint.Align.LEFT);
mLoc_X_1 = getWidth() / 2;//第一屏的坐標(biāo)
?
//不少于兩屏
?
mIndex_2 = txtCacheList.size() > 1 ? 1 : 0;//第二屏的文字角標(biāo)
mIndex_3 = mIndex_2 + 1 < txtCacheList.size() ? mIndex_2 + 1 : 0;//第三屏的文字角標(biāo)
}
?
/// 繪制文字
public void onDrawUI() {
if (txtCacheList.size() > 0 && holder != null) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(bgColor);
?
//水平滾動,往左
int size = txtCacheList.size();
if (txtCacheList.size() > 0) {
mLoc_X_1 = mLoc_X_1 - offsetDis;
?
// 類似傳送帶方式的移動
MarqueeBean bean1 = txtCacheList.get(mIndex_1 % size);
String str1 = bean1.getMsg();
MarqueeBean bean2 = txtCacheList.get(mIndex_2 % size);
String str2 = bean2.getMsg();
MarqueeBean bean3 = txtCacheList.get(mIndex_3 % size);
String str3 = bean3.getMsg();
?
float mX_2 = bean1.getLen() + mLoc_X_1;
float mX_3 = bean2.getLen() + mX_2;
canvas.drawText(str1, mLoc_X_1, mLoc_Y_1, paint);
canvas.drawText(str2, mX_2, mLoc_Y_1, paint);
canvas.drawText(str3, mX_3, mLoc_Y_1, paint);
?
if (mX_2 < 0) {
// 變化游標(biāo)
mIndex_1 = mIndex_2;
mIndex_2 = mIndex_3;
mIndex_3 = (mIndex_2 + 1) % txtCacheList.size();
// 變化坐標(biāo)
mLoc_X_1 = mX_2;
}
}
holder.unlockCanvasAndPost(canvas);
}
}
?
/**
* 滾動任務(wù)
*/
private Runnable mScheduledRun = new Runnable() {
@Override
public void run() {
while (!isInterrupted) {
synchronized (txtCacheList) {
if (txtCacheList.size() <= 0 || speed <= 0 || getVisibility() != View.VISIBLE) {
//小于一屏或者滾動速度為0,那么中斷滾動
stopRolling();
break;
}
}
if (!isRolling) {
isRolling = true;
}
try {
Thread.sleep(40);
onDrawUI();//每隔40毫秒重繪視圖
} catch (Throwable e) {
e.printStackTrace();
}
}
isRolling = false;
}
};
?
/**
* 意圖事件處理
*/
private Handler mEventHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0) {
Bundle data = msg.getData();
if (data != null) {
ArrayList<MarqueeBean> list = data.getParcelableArrayList("data");
LogUtils.v(TAG, "收到數(shù)據(jù) == " + (list == null ? null : list.size()));
if (list != null) {
txtCacheList.addAll(list);
reset();
startRolling();
?
}
}
} else if (msg.what == 1) {
if (holder != null) {
//初始化背景色
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(bgColor);
}
holder.unlockCanvasAndPost(canvas);
}
reset();
?
if (txtCacheList.size() == 0 && !TextUtils.isEmpty(oldStr)) {
//先停止?jié)L動,然后才能設(shè)置文字
if (totalLength <= 0) {
totalLength = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
LogUtils.v(TAG, "onlayout 總長 = " + totalLength + " 字 = " + oldStr.length());
new FormatTextTask(mEventHandler, totalLength, textSize).execute(oldStr);
}
?
}
return false;
}
});
?
/**
* 格式化文字任務(wù)
*/
private static class FormatTextTask extends AsyncTask<String, Void, ArrayList<MarqueeBean>> {
?
//控件對應(yīng)一屏的長度,如果是水平滾動,那么就是一屏寬度,如果是垂直滾動,就是一屏高度,必須有確切的寬或高
private float contentLength;
private float textSize;//字體大小
private Handler mEventHandler;
?
public FormatTextTask(Handler mEventHandler, float contentLength, float textSize) {
this.mEventHandler = mEventHandler;
this.contentLength = contentLength;
this.textSize = textSize;
}
?
@Override
protected ArrayList<MarqueeBean> doInBackground(String... strings) {
if (strings == null || strings.length <= 0) {
return null;
}
LogUtils.v(TAG, "滾動方向的長度 = " + contentLength);
if (contentLength <= 0) {
//必須有確切的寬或高
return null;
}
String str = strings[0]; // 需要格式的文字
if (str == null || str.length() == 0) {
return null;
}
String formatStr;
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
formatStr = Html.fromHtml(str, Html.FROM_HTML_MODE_LEGACY).toString();
} else {
formatStr = Html.fromHtml(str).toString();
}
} catch (Throwable e) {
LogUtils.log(TAG, "字符無法轉(zhuǎn)編碼", LogUtils.LogType.FLAG_LOG_V, e);
formatStr = str;
}
?
ArrayList<MarqueeBean> list = new ArrayList<>();
Rect rect = new Rect();
TextPaint paint = new TextPaint();
paint.setTextAlign(Paint.Align.LEFT);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
?
int start = 0, len = 20;
int index = 0;
do {
int end = (start + len);
if (end > formatStr.length()) {
end = formatStr.length();
}
String cacheStr = formatStr.substring(start, end);
float len1 = Layout.getDesiredWidth(cacheStr, 0, cacheStr.length(), paint);
MarqueeBean bean = new MarqueeBean(cacheStr, len1);
list.add(bean);
start = end;
index++;
} while (start < formatStr.length());
LogUtils.w(TAG, "拆分的字符 =======================>> " + list.size());
return list;
}
?
@Override
protected void onPostExecute(ArrayList<MarqueeBean> list) {
if (mEventHandler != null) {
Message message = mEventHandler.obtainMessage(0);
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("data", list);
message.setData(bundle);
mEventHandler.sendMessage(message);
}
}
?
// 獲取字體高度
private static float getFontHeight(float fontSize) {
Paint paint = new Paint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(fontSize);
FontMetrics fm = paint.getFontMetrics();
return (float) Math.ceil(fm.descent - fm.ascent);
}
}
?
?
private static int dp2px(Resources res, float dpValue) {
if (res == null) return -1;
final float scale = res.getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
?
?
///////////////////////////////////// out public method //////////////////////////////////////
?
private Runnable textRun;
/**
* 設(shè)置文字
*
* @param s 文字
*/
public void setText(final String s) {
//先停止?jié)L動,然后才能設(shè)置文字
stopRolling();
txtCacheList.clear();
if (textRun != null) {
removeCallbacks(textRun);
}
if (TextUtils.isEmpty(s)) {
oldStr = null;
return;
}
post(textRun = new Runnable() {
@Override
public void run() {
int count = Math.round(getWidth() / paint.measureText("H")) * 3;
LogUtils.w("當(dāng)前 = " + s.length() + " 總共不能小于 = " + count);
if (s != null && s.length() > 0 && s.length() < count) {
oldStr = "";
int num = count / s.length();
for (int index = 0; index < num; index++) {
oldStr += s + " ● ";
}
oldStr += s;
} else {
oldStr = s;
}
mEventHandler.removeMessages(1);
mEventHandler.sendEmptyMessageDelayed(1, 500);
}
});
}
?
/**
* 設(shè)置字體大小
*
* @param textSize 文字大小
*/
public void setTextSize(float textSize) {
this.textSize = textSize;
paint.setTextSize(textSize);
}
?
/**
* 設(shè)置文字顏色
*
* @param textColor
*/
public void setTextColor(int textColor) {
this.textColor = textColor;
paint.setColor(textColor);
}
?
/**
* 設(shè)置背景顏色
*
* @param bgColor 背景顏色
*/
@Override
public void setBackgroundColor(int color) {
// super.setBackgroundColor(color);
this.bgColor = color;
}
?
/**
* 設(shè)置滾動速度
*
* @param speed
*/
public void setSpeed(float speed) {
if (speed > MAX_SPEED || speed < MIN_SPEED) {
throw new IllegalArgumentException(
String.format(Locale.getDefault(),
"Speed was invalid integer, it must between %d and %d", MIN_SPEED, MAX_SPEED));
} else {
this.speed = speed;
offsetDis = speed * 2;
}
}
?
/**
* 開始滾動
*/
private void startRolling() {
try {
if (txtCacheList.size() < 1) {
//如果文字不夠一屏,不移動
return;
}
if (getVisibility() != View.VISIBLE) {
//如果不顯示,就不滾動
return;
}
LogUtils.v(TAG, "startRolling -------- ");
if (mScheduledThread == null) {
mScheduledThread = new Thread(mScheduledRun, "schedule");
isInterrupted = false;
mScheduledThread.start();
}
} catch (Throwable e) {
LogUtils.log(TAG, "start rolling error", LogUtils.LogType.FLAG_LOG_E, e);
}
}
?
/**
* 停止?jié)L動
*/
public void stopRolling() {
try {
LogUtils.v(TAG, "stopRolling -------- ");
if (mScheduledThread != null) {
isInterrupted = true;
mScheduledThread.interrupt();
mScheduledThread = null;
}
} catch (Throwable e) {
LogUtils.log(TAG, "stop rolling error", LogUtils.LogType.FLAG_LOG_E, e);
}
}
?
/**
* 銷毀滾動
*/
public void destroyRolling() {
Log.v(TAG, "destroyRolling -------- ");
try {
startRolling();
txtCacheList.clear();
} catch (Throwable e) {
LogUtils.log(TAG, "destroy rolling error", LogUtils.LogType.FLAG_LOG_E, e);
}
}
?
@Override
protected void onDetachedFromWindow() {
if (holder != null) {
holder.removeCallback(this);
}
super.onDetachedFromWindow();
}
public static class MarqueeBean implements Parcelable {
private String msg;
private float len;
?
public MarqueeBean(String msg, float len) {
this.msg = msg;
this.len = len;
}
?
public String getMsg() {
return msg;
}
?
public float getLen() {
return len;
}
}
}
這里需要注意的是,修改了文字大小,必須重新設(shè)置字符串進(jìn)行文字長度計(jì)算。
全部示例都在這邊了,因?yàn)闀簳r(shí)沒時(shí)間維護(hù)開源庫,所以就不上傳代碼了,有需要的話再聯(lián)系我。