美文网首页
无限滚动字幕参考方案

无限滚动字幕参考方案

作者: 沐小木沐 | 来源:发表于2020-12-11 17:41 被阅读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;
            }
        }
    }
    

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

    相关文章

      网友评论

          本文标题:无限滚动字幕参考方案

          本文链接:https://www.haomeiwen.com/subject/jjyjgktx.html