無限滾動字幕參考方案

因?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ī)則如下圖所示

image

于是,我們就有了以下計(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)系我。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 各大字幕組常用的時(shí)間軸軟件:time machine(人人)、popsub(鳳凰天使)、aegisub,簡單的字幕...
    朱細(xì)細(xì)閱讀 45,444評論 4 25
  • 當(dāng)遇到一個(gè)通過橫幅滾動文字來做公告通知的時(shí)候,一開始想著用TextView自帶的效果android:ellipsi...
    霧中的影子閱讀 1,906評論 0 1
  • 久違的晴天,家長會。 家長大會開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,788評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,798評論 0 11
  • 可愛進(jìn)取,孤獨(dú)成精。努力飛翔,天堂翱翔。戰(zhàn)爭美好,孤獨(dú)進(jìn)取。膽大飛翔,成就輝煌。努力進(jìn)取,遙望,和諧家園。可愛游走...
    趙原野閱讀 3,443評論 1 1

友情鏈接更多精彩內(nèi)容