无限滚动字幕参考方案

2020-12-11  本文已影响0人  沐小木沐

因为前段时间在做广告机,正好需要用到字幕滚动,期间也踩了一些坑,所以这边就讲一下安卓字幕滚动的几种实现方案。(这边只讲水平滚动,不讲垂直滚动,因为原理是类似的)

1.最简单的实现方案

首先,我们可以直接使用系统的 TextView 控件。通过查询系统源码,我们可以发现,在TextView控件里面有个Marquee.class 内部类,而这个类又是控制 TextView 文本滚动的。所以我们可以这样添加一个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);//循环次数,-1无限循环 

同时,我们根据源码要求,必须满足 isFocused() || isSelected() ,所以当需要循环的时候,可以调用 textView.setSelected(true);

2.稍微复杂的方案

如果需要对文本滚动速度进行调节的,那么使用TextView 的限制就比较大了,当然你也可以通过使用hook或者修改源码等一系列方案来修改TextView,不过那太麻烦了,除非你愿意。所以,我们可以考虑通过定制一个View ,然后设定画笔画布,绘制指定的文本,再做个定时器,让文本滚动起来。


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);      //实际的视图高
        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.有点麻烦的方案

通过以上方案,大概可以解决百分之85的水平字幕滚动需求了。当然,还有各种特殊情况,比如部分主板绘制超过千字的文本,会出现左起点文字重叠的现象,

特殊重叠字幕

通过多次试验,发现这些主板对应的系统必须保证 Canves.drawText() 函数每次绘制的文字不能超过指定的字数(大部分是千字)。那么,针对这一原则,为了保证每次绘制的文字不能超过千字,所以需要对文字进行裁切。那么这里就出现了一个问题。我们只要一个字幕滚动,切成多份的文字之后,要保证每段文字可以上下衔接同步滚动,就必须对文字的宽高进行计算。

这里扩展延伸一下安卓字符宽高的计算方案:

首先,我们需要知道每个字符的计算规则如下图所示

image

于是,我们就有了以下计算文字宽度的方案:

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();

同时,经过多种主板测试,我们发现测量文字宽高必须是屏幕可容纳字数之内才是较为精确的。比如屏幕最多容纳100个字的情况下,测量100个字符的宽高是准确的,但是测量超过这个数量之后,可能出现长度偏大或偏小的情况。

考虑到我们把字符串分成n段,而每一段的长度可能不一样,假设每段都接近一个屏幕宽,然后给每段设定一个x坐标,那么最少需要三段字幕。当滚动第一段完毕的时候,第二段完整展示在界面上面,第三段可能显示出来(即第一段的x为负的第一段宽度,第二段的x为0,第三段的x为第二段的宽)。于是,我们就可以构造这样的控件。


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坐标
    private float mLoc_Y_1 = 0;//第一屏的y坐标
​
    private float offsetDis = 0;//偏移量,以速度为基准
​
    private int mIndex_1 = 0;//第一屏的文字角标
    private int mIndex_2 = 1;//第二屏的文字角标
    private int mIndex_3 = 2;//第三屏的文字角标
​
    private boolean isRolling = false;//是否在滚动
    private boolean isInterrupted = true;//是否停止线程循环
​
    private float totalLength = 0.0f;// 显示总长度
    private int totalTimes = -1; // 滚动次数
    private int doneCount = 0;//准备执行滚动的次数
​
    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);
    }
​
    /**
     * 重置参数
     */
    private void reset() {
        if (txtCacheList.size() <= 0) return;
​
        mIndex_1 = 0;//第一屏的文字角标
        simpleHeight = FormatTextTask.getFontHeight(textSize);
​
        //fixme 这边先不考虑内边距
        // 水平滚动
        totalLength = getWidth();
​
        //定高
        mLoc_Y_1 = getHeight() / 2 + simpleHeight / 3;
​
        paint.setTextAlign(Paint.Align.LEFT);
        mLoc_X_1 = getWidth() / 2;//第一屏的坐标
​
        //不少于两屏
​
        mIndex_2 = txtCacheList.size() > 1 ? 1 : 0;//第二屏的文字角标
        mIndex_3 = mIndex_2 + 1 < txtCacheList.size() ? mIndex_2 + 1 : 0;//第三屏的文字角标
    }
​
    /// 绘制文字
    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) {
                    // 变化游标
                    mIndex_1 = mIndex_2;
                    mIndex_2 = mIndex_3;
                    mIndex_3 = (mIndex_2 + 1) % txtCacheList.size();
                    // 变化坐标
                    mLoc_X_1 = mX_2;
                }
            }
            holder.unlockCanvasAndPost(canvas);
        }
    }
​
    /**
     * 滚动任务
     */
    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, "收到数据 == " + (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)) {
                    //先停止滚动,然后才能设置文字
                    if (totalLength <= 0) {
                        totalLength = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
                    }
                    LogUtils.v(TAG, "onlayout 总长 = " + totalLength + " 字 = " + oldStr.length());
                    new FormatTextTask(mEventHandler, totalLength, textSize).execute(oldStr);
                }
​
            }
            return false;
        }
    });
​
    /**
     * 格式化文字任务
     */
    private static class FormatTextTask extends AsyncTask<String, Void, ArrayList<MarqueeBean>> {
​
        //控件对应一屏的长度,如果是水平滚动,那么就是一屏宽度,如果是垂直滚动,就是一屏高度,必须有确切的宽或高
        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, "字符无法转编码", 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;
    /**
     * 设置文字
     *
     * @param s 文字
     */
    public void setText(final String s) {
        //先停止滚动,然后才能设置文字
        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("当前 = " + 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);
            }
        });
    }
​
    /**
     * 设置字体大小
     *
     * @param textSize 文字大小
     */
    public void setTextSize(float textSize) {
        this.textSize = textSize;
        paint.setTextSize(textSize);
    }
​
    /**
     * 设置文字颜色
     *
     * @param textColor
     */
    public void setTextColor(int textColor) {
        this.textColor = textColor;
        paint.setColor(textColor);
    }
​
    /**
     * 设置背景颜色
     *
     * @param bgColor 背景颜色
     */
    @Override
    public void setBackgroundColor(int color) {
//        super.setBackgroundColor(color);
        this.bgColor = color;
    }
​
    /**
     * 设置滚动速度
     *
     * @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);
        }
    }
​
    /**
     * 停止滚动
     */
    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;
        }
    }
}

这里需要注意的是,修改了文字大小,必须重新设置字符串进行文字长度计算。
全部示例都在这边了,因为暂时没时间维护开源库,所以就不上传代码了,有需要的话再联系我。

上一篇下一篇

猜你喜欢

热点阅读