圣光?。⌒∪饪次仪么a----利用SurfaceView與線程搭配實(shí)現(xiàn)垂直滾動(dòng)顯示文字跑馬燈view效果

利用SurfaceView實(shí)現(xiàn)垂直滾動(dòng)顯示文字跑馬燈view效果

作者:圣光啊那個(gè)敵人值得一戰(zhàn)

前一陣在做的項(xiàng)目有一個(gè)循環(huán)滾動(dòng)顯示通知內(nèi)容的需求,當(dāng)時(shí)趕時(shí)間,就簡單的套到了ScrollView里然后計(jì)算控件高度讓它滾動(dòng)顯示,但是問題明顯是很多的,因?yàn)閿?shù)據(jù)更新的方式比較奇葩(服務(wù)端通知客戶端),所以經(jīng)常會(huì)有數(shù)據(jù)刷新重新加載view造成高度計(jì)算錯(cuò)誤的情況出現(xiàn),而且這個(gè)項(xiàng)目是運(yùn)行在公司自己生產(chǎn)的設(shè)備上當(dāng)做考勤機(jī)來使用,所以正常情況下會(huì)一直運(yùn)行10幾天。。。所以在壓力測試的時(shí)候這個(gè)問題相當(dāng)?shù)耐怀觥?/p>

所以嘍。。。這個(gè)問題不改我會(huì)讓經(jīng)理打死的。本來就想著在網(wǎng)上找個(gè)現(xiàn)成的庫用一下,有輪子不用白不用啊,但是搜了搜,都只有單行滾動(dòng)切換的,就算在其基礎(chǔ)上改,不但受限于別人的思路,也會(huì)讓效果大打折扣。所以在工位上思索了下(我強(qiáng)行不改這個(gè)需求的風(fēng)險(xiǎn)有多大?恩,蠻高的),決定自己實(shí)現(xiàn)。

本來決定思路的時(shí)候是想著繼承個(gè)view然后開線程循環(huán)更新文字位置顯示來著,但是感覺好像大概會(huì)讓繪制過于頻繁(其實(shí)還好,就是覺得),這時(shí)候想起來了以前繪制更新大量圖片的時(shí)候用到的SurfaceView與線程的搭配蠻舒服的,所以就拍腦袋決定,就這個(gè)了!

要用SurfaceView來實(shí)現(xiàn)這個(gè)需求,我們得看一下一個(gè)回調(diào)接口,SurfaceHolder.Callback,其注釋描述如下:

/**
     * A client may implement this interface to receive information about
     * changes to the surface.  When used with a {@link SurfaceView}, the
     * Surface being held is only available between calls to
     * {@link #surfaceCreated(SurfaceHolder)} and
     * {@link #surfaceDestroyed(SurfaceHolder)}.  The Callback is set with
     * {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
     */
    public interface Callback {
        /**
         * This is called immediately after the surface is first created.
         * Implementations of this should start up whatever rendering code
         * they desire.  Note that only one thread can ever draw into
         * a {@link Surface}, so you should not draw into the Surface here
         * if your normal rendering will be in another thread.
         * 
         * @param holder The SurfaceHolder whose surface is being created.
         */
        public void surfaceCreated(SurfaceHolder holder);

        /**
         * This is called immediately after any structural changes (format or
         * size) have been made to the surface.  You should at this point update
         * the imagery in the surface.  This method is always called at least
         * once, after {@link #surfaceCreated}.
         * 
         * @param holder The SurfaceHolder whose surface has changed.
         * @param format The new PixelFormat of the surface.
         * @param width The new width of the surface.
         * @param height The new height of the surface.
         */
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height);

        /**
         * This is called immediately before a surface is being destroyed. After
         * returning from this call, you should no longer try to access this
         * surface.  If you have a rendering thread that directly accesses
         * the surface, you must ensure that thread is no longer touching the 
         * Surface before returning from this function.
         * 
         * @param holder The SurfaceHolder whose surface is being destroyed.
         */
        public void surfaceDestroyed(SurfaceHolder holder);
    }

來大家看,這個(gè)接口里面需要實(shí)現(xiàn)三個(gè)方法,而且名字起得都很直白親民,創(chuàng)建方法,改變方法,銷毀方法,這就意味著我們能夠在線程改變SurfaceView的holder內(nèi)容時(shí)根據(jù)這三個(gè)方法的實(shí)現(xiàn)來管理SurfaceView,這樣就能在創(chuàng)建SurfaceView的時(shí)候得到holder,改變的時(shí)候檢查線程情況,銷毀的時(shí)候處理線程,比如下面這樣:

 @Override
    public void surfaceCreated(SurfaceHolder holder) {
        this.holder = holder;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mThread != null)
            mThread.isRun = true;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mThread != null)
            mThread.isRun = false;
    }

上面的mThread就是用來繪制并提交文字位置實(shí)現(xiàn)滾動(dòng)效果的線程了,而開始繪制前,我們肯定不能瞎畫啊,所以得先初始化一下滾動(dòng)的效果參數(shù)等

public VerticalMarqueeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        if (isInEditMode()) {
            //防止編輯預(yù)覽界面報(bào)錯(cuò)
            return;
        }
        init(attrs, defStyleAttr);
    }

    private float mTextSize = 100; //字體大小

    private int mTextColor = Color.RED; //字體的顏色

    private boolean mIsRepeat;//是否重復(fù)滾動(dòng)

    private int mStartPoint;// 開始滾動(dòng)的位置  0是從上面開始   1是從下面開始

    private int mDirection;//滾動(dòng)方向 0 向上滾動(dòng)   1向下滾動(dòng)

    private int mSpeed;//滾動(dòng)速度

    private void init(AttributeSet attrs, int defStyleAttr) {

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.VerticalMarqueeTextView, defStyleAttr, 0);
        mTextColor = a.getColor(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_textColor, Color.RED);
        mTextSize = a.getDimension(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_textSize, 48);
        mIsRepeat = a.getBoolean(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_isRepeat, false);
        mStartPoint = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_startPoint, 0);
        mDirection = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_direction, 0);
        mSpeed = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_speed, 20);
        if (mSpeed < 20) {
            mSpeed = 20;
        }
        a.recycle();

        point = new Point(0, 0);
        holder = this.getHolder();
        holder.addCallback(this);
        mTextPaint = new TextPaint();
        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextAlign(Paint.Align.LEFT);
        setZOrderOnTop(true);//使surfaceview放到最頂層
        getHolder().setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度
    }

attr中的屬性定義如下:

<declare-styleable name="VerticalMarqueeTextView">
        <attr name="VerticalMarqueeTextView_textColor" format="color" />
        <attr name="VerticalMarqueeTextView_textSize" format="dimension" />
        <attr name="VerticalMarqueeTextView_isRepeat" format="boolean" />
        <attr name="VerticalMarqueeTextView_startPoint" format="integer" >
            <enum name="start" value="0" />
            <enum name="end" value="1" />
        </attr>
        <attr name="VerticalMarqueeTextView_direction" format="integer" >
            <enum name="up" value="0" />
            <enum name="down" value="1" />
        </attr>
        <attr name="VerticalMarqueeTextView_speed" format="integer" />
    </declare-styleable>

獲取完了在布局里就設(shè)置好的屬性后,我們再初始化些point類啊,TextPaint類,給holder設(shè)置回調(diào)啊就差不多了,初始工作這就算完成,哎~戈薇剛才是不是在心里罵了句扯淡?對,我們還沒有初始化位置信息,話說我為什么不把位置信息也一并放入構(gòu)造函數(shù)里初始化呢?以為我的需求是個(gè)通知啊各位,它是會(huì)經(jīng)常變得,所以,我得在每次文本內(nèi)容改變的時(shí)候計(jì)算,比如這樣:

public void setText(String msg) {
        if (!TextUtils.isEmpty(msg)) {
            measurementsText(msg);
        }
    }

    protected void measurementsText(String msg) {
        margueeString = msg;
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setStrokeWidth(0.5f);
        mTextPaint.setFakeBoldText(true);
        textWidth = (int) mTextPaint.measureText(margueeString);//因?yàn)橛羞@句話,所以得等控件繪制完在進(jìn)行通知顯示,對,就是用handler
        int height = getHeight() - getPaddingTop() - getPaddingBottom();
        if (mStartPoint == 0)
            currentY = 50;
        else
            currentY = height;
    }

話說,各位看見我上面加的注釋了沒?啥?我還加注釋了?哼,大拳拳捶你胸口!都不認(rèn)真看!

恩,把被錘吐血的同學(xué)拉下去,我們繼續(xù)。各位看,在上面我們獲取完了初始的位置后,就真的是差不多了,現(xiàn)在只需要開個(gè)線程不停循環(huán)計(jì)算然后繪制并提交改變就行了,首先,我們再初始化一下(主要這個(gè)方法寫雜了,沒法歸類啊)

/**
     * 開始滾動(dòng)
     *
     * @param isStop 是否停止顯示
     * @param sec    停止顯示時(shí)間
     */
    public void startScroll(boolean isStop, int sec) {
        if (mThread != null) {
            return;
        }
        this.isStop = isStop;
        this.sec = sec * 1000;
        /*
         * 設(shè)置繪制多行文本所需參數(shù)
         *
         * @param string      文本
         * @param textPaint   文本筆
         * @param canvas      canvas
         * @param point       點(diǎn)
         * @param width       寬度
         * @param align       layout的對齊方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三種。
         * @param spacingmult 相對行間距,相對字體大小,1.5f表示行間距為1.5倍的字體高度。
         * @param spacingadd  在基礎(chǔ)行距上添加多少
         * @param includepad  參數(shù)未知(不知道啥,反正填false)
         * @param height      繪制高度
         */
        staticLayout = new StaticLayout(margueeString, mTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.5f, 0, false);

        //獲取所有字的累加高度
        textHeight = staticLayout.getHeight();
        isFirstDraw = true;
        mThread = new MarqueeViewThread(holder);//創(chuàng)建一個(gè)繪圖線程
        mThread.isRun = true;
        mThread.start();
    }

在這里不得不說一下StaticLayout這個(gè)類,大家知道,一般顯示文字都只是顯示一行或者定好TextView的寬度好讓字多的時(shí)候換行,但是我們這里是沒有用到TextView的,所以文字換行這個(gè)事情就顯得很麻煩了,但是好在Android已經(jīng)為我們提供好了這個(gè)叫做StaticLayout的類,它的注釋我就不給大家看了(主要我沒看懂),主要是用這個(gè)類方便換行,它會(huì)根據(jù)高度適配繪制多行文本,講道理在坐的各位,可以的。

說完參數(shù)和初始位置后,就到了我們的重點(diǎn)了各位(敲黑板!),那就是本次的重頭戲,線程循環(huán)繪制文本了撒,例子如下:

/**
     * 是否繼續(xù)滾動(dòng)
     */
    private boolean isGo = true;

    /**
     * 線程
     */
    class MarqueeViewThread extends Thread {

        private final SurfaceHolder holder;

        public boolean isRun;//是否在運(yùn)行


        public MarqueeViewThread(SurfaceHolder holder) {
            this.holder = holder;
            isRun = true;
        }

        public void onDraw() {
            try {
                synchronized (holder) {
                    if (TextUtils.isEmpty(margueeString)) {
                        Thread.sleep(1000);//睡眠時(shí)間為1秒
                        return;
                    }
                    if (isGo) {
                        final Canvas canvas = holder.lockCanvas();
                        int paddingTop = getPaddingTop();
                        int paddingBottom = getPaddingBottom();

                        int contentHeight = getHeight() - paddingTop - paddingBottom;

                        if (mDirection == 0) {//向上滾動(dòng)
                            if (currentY <= -textHeight) {
                                currentY = contentHeight;
                                if (!mIsRepeat) {//如果是不重復(fù)滾動(dòng)
                                    mHandler.sendEmptyMessage(ROLL_OVER);
                                    holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                                    return;
                                }
                            } else {
                                currentY -= sepY;
                            }
                            currentY -= sepY;
                        } else {//  向下滾動(dòng)
                            if (currentY >= textHeight + sepY + 10) {
                                currentY = 0;
                                if (!mIsRepeat) {//如果是不重復(fù)滾動(dòng)
                                    mHandler.sendEmptyMessage(ROLL_OVER);
                                    holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                                    return;
                                }
                            } else {
                                currentY += sepY;
                            }
                        }

                        if (canvas != null) {
                            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//繪制透明色
                            textCenter(canvas, currentY);
                            holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                            if (isFirstDraw) {
                                mHandler.sendEmptyMessageDelayed(STOP_ROLL, 50);//暫停顯示5秒
                                isFirstDraw = false;
                            }
                        }
                        Thread.sleep(mSpeed);//睡眠時(shí)間為移動(dòng)的頻率~~
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            while (isRun) {
                onDraw();
            }
        }
    }

其中的計(jì)算方式大體上就是動(dòng)態(tài)的改變當(dāng)前的文字高度進(jìn)行繪制,文字移動(dòng)的頻率就是線程的睡眠頻率,改變后鎖定并提交改變~
大體效果如下:

GIF.gif

整體例子如下:
···
/**

  • Created by lip on 2016/12/23.
  • <p>
  • 豎直滾動(dòng)跑馬燈
    */

public class VerticalMarqueeView extends SurfaceView implements SurfaceHolder.Callback {

public Context mContext;

private float mTextSize = 100; //字體大小

private int mTextColor = Color.RED; //字體的顏色

private boolean mIsRepeat;//是否重復(fù)滾動(dòng)

private int mStartPoint;// 開始滾動(dòng)的位置  0是從上面開始   1是從下面開始

private int mDirection;//滾動(dòng)方向 0 向上滾動(dòng)   1向下滾動(dòng)

private int mSpeed;//滾動(dòng)速度

private SurfaceHolder holder;

private TextPaint mTextPaint;

private MarqueeViewThread mThread;

private String margueeString;

private int textWidth = 0, textHeight = 0;

public int currentY = 0;// 當(dāng)前y的位置

public double sepY = 1;//每一步滾動(dòng)的距離

private Point point;//點(diǎn),沒啥用,懶得弄了

private StaticLayout staticLayout;//繪制多行文本所需類

private boolean isFirstDraw = true;//是否為某條文本的第一次繪制~~

private boolean isStop = false;
private int sec = 5000;

public VerticalMarqueeView(Context context) {
    this(context, null);
}

public VerticalMarqueeView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public VerticalMarqueeView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.mContext = context;
    if (isInEditMode()) {
        //防止編輯預(yù)覽界面報(bào)錯(cuò)
        return;
    }
    init(attrs, defStyleAttr);
}

private void init(AttributeSet attrs, int defStyleAttr) {

    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.VerticalMarqueeTextView, defStyleAttr, 0);
    mTextColor = a.getColor(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_textColor, Color.RED);
    mTextSize = a.getDimension(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_textSize, 48);
    mIsRepeat = a.getBoolean(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_isRepeat, false);
    mStartPoint = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_startPoint, 0);
    mDirection = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_direction, 0);
    mSpeed = a.getInt(R.styleable.VerticalMarqueeTextView_VerticalMarqueeTextView_speed, 20);
    if (mSpeed < 5) {
        mSpeed = 5;
    }
    a.recycle();

    point = new Point(0, 0);
    holder = this.getHolder();
    holder.addCallback(this);
    mTextPaint = new TextPaint();
    mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setTextAlign(Paint.Align.LEFT);
    setZOrderOnTop(true);//使surfaceview放到最頂層
    getHolder().setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度
}

public void setText(String msg) {
    if (!TextUtils.isEmpty(msg)) {
        measurementsText(msg);
    }
}

protected void measurementsText(String msg) {
    margueeString = msg;
    mTextPaint.setTextSize(mTextSize);
    mTextPaint.setColor(mTextColor);
    mTextPaint.setStrokeWidth(0.5f);
    mTextPaint.setFakeBoldText(true);
    textWidth = (int) mTextPaint.measureText(margueeString);
    int height = getHeight() - getPaddingTop() - getPaddingBottom();
    if (mStartPoint == 0)
        currentY = 50;
    else
        currentY = height;
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    this.holder = holder;
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    if (mThread != null)
        mThread.isRun = true;
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    if (mThread != null)
        mThread.isRun = false;
}

/**
 * 線程是否在運(yùn)行
 *
 * @return 結(jié)果
 */
public boolean isThreadRunning() {
    return mThread != null && mThread.isRun && !mThread.isInterrupted();
}

/**
 * 開始滾動(dòng)
 *
 * @param isStop 是否停止顯示
 * @param sec    停止顯示時(shí)間
 */
public void startScroll(boolean isStop, int sec) {
    if (mThread != null) {
        return;
    }
    this.isStop = isStop;
    this.sec = sec * 1000;
    /*
     * 設(shè)置繪制多行文本所需參數(shù)
     *
     * @param string      文本
     * @param textPaint   文本筆
     * @param canvas      canvas
     * @param point       點(diǎn)
     * @param width       寬度
     * @param align       layout的對齊方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三種。
     * @param spacingmult 相對行間距,相對字體大小,1.5f表示行間距為1.5倍的字體高度。
     * @param spacingadd  在基礎(chǔ)行距上添加多少
     * @param includepad  參數(shù)未知(不知道啥,反正填false)
     * @param height      繪制高度
     */
    staticLayout = new StaticLayout(margueeString, mTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.5f, 0, false);

    //獲取所有字的累加高度
    textHeight = staticLayout.getHeight();
    isFirstDraw = true;
    mThread = new MarqueeViewThread(holder);//創(chuàng)建一個(gè)繪圖線程
    mThread.isRun = true;
    mThread.start();
}

/**
 * 停止?jié)L動(dòng)
 */
public void stopScroll() {
    if (mThread != null) {
        mThread.isRun = false;
    }
    mThread = null;
}

/**
 * 暫停播放
 */
public void pauseScroll() {
    if (mThread != null) {
        mThread.isRun = false;
        mThread = null;
    }
}

/**
 * 恢復(fù)播放
 */
public void restartRoll() {
    mThread = new MarqueeViewThread(holder);
    mThread.isRun = true;
    mThread.start();
}

/**
 * 請空內(nèi)容
 */
public void clearText() {
    if (mThread != null && mThread.isRun) {
        margueeString = "";
    }
}

/**
 * 是否繼續(xù)滾動(dòng)
 */
private boolean isGo = true;

/**
 * 線程
 */
class MarqueeViewThread extends Thread {

    private final SurfaceHolder holder;

    public boolean isRun;//是否在運(yùn)行


    public MarqueeViewThread(SurfaceHolder holder) {
        this.holder = holder;
        isRun = true;
    }

    public void onDraw() {
        try {
            synchronized (holder) {
                if (TextUtils.isEmpty(margueeString)) {
                    Thread.sleep(1000);//睡眠時(shí)間為1秒
                    return;
                }
                if (isGo) {
                    final Canvas canvas = holder.lockCanvas();
                    int paddingTop = getPaddingTop();
                    int paddingBottom = getPaddingBottom();

                    int contentHeight = getHeight() - paddingTop - paddingBottom;

                    if (mDirection == 0) {//向上滾動(dòng)
                        if (currentY <= -textHeight) {
                            currentY = contentHeight;
                            if (!mIsRepeat) {//如果是不重復(fù)滾動(dòng)
                                mHandler.sendEmptyMessage(ROLL_OVER);
                                holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                                return;
                            }
                        } else {
                            currentY -= sepY;
                        }
                        currentY -= sepY;
                    } else {//  向下滾動(dòng)
                        if (currentY >= textHeight + sepY + 10) {
                            currentY = 0;
                            if (!mIsRepeat) {//如果是不重復(fù)滾動(dòng)
                                mHandler.sendEmptyMessage(ROLL_OVER);
                                holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                                return;
                            }
                        } else {
                            currentY += sepY;
                        }
                    }

                    if (canvas != null) {
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//繪制透明色
                        textCenter(canvas, currentY);
                        holder.unlockCanvasAndPost(canvas);//結(jié)束鎖定畫圖,并提交改變。
                        if (isFirstDraw) {
                            mHandler.sendEmptyMessageDelayed(STOP_ROLL, 50);//暫停顯示5秒
                            isFirstDraw = false;
                        }
                    }
                    Thread.sleep(mSpeed);//睡眠時(shí)間為移動(dòng)的頻率~~
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (isRun) {
            onDraw();
        }
    }
}

/**
 * 繪制多行文本
 *
 * @param canvas canvas
 * @param height 繪制高度
 */
private void textCenter(Canvas canvas, int height) {
    canvas.save();
    canvas.translate(0, height);
    staticLayout.draw(canvas);
    canvas.restore();
}

public static final int ROLL_OVER = 100;//一條播放完畢
public static final int STOP_ROLL = 200;//停止?jié)L動(dòng)
public static final int START_ROLL = 300;//開始滾動(dòng)
public static final int STOP_THREAT = 400;//停止線程a
Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        switch (msg.what) {
            case ROLL_OVER:
                stopScroll();
                if (mOnMargueeListener != null) {
                    mOnMargueeListener.onRollOver();
                }
                break;
            case STOP_ROLL:
                isGo = false;
                mHandler.sendEmptyMessageDelayed(START_ROLL, sec);
                break;
            case START_ROLL:
                isGo = true;
                break;
            case STOP_THREAT:
                stopScroll();
                break;
        }
    }
};

/**
 * dip轉(zhuǎn)換為px
 *
 * @param context
 * @param dpValue
 * @return
 */
public static int dip2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}

public void reset() {
    int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
    int contentHeight = getHeight() - getPaddingTop() - getPaddingBottom();
    if (mStartPoint == 0)
        currentY = 0;
    else
        currentY = contentHeight;
}

/**
 * 滾動(dòng)回調(diào)
 */
public interface OnMargueeListener {
    void onRollOver();//滾動(dòng)完畢
}

OnMargueeListener mOnMargueeListener;

public void setOnMargueeListener(OnMargueeListener mOnMargueeListener) {
    this.mOnMargueeListener = mOnMargueeListener;
}

}

VerticalMarqueeView

maven

allprojects { repositories { ... maven { url 'https://jitpack.io' } } }

Add the dependency

dependencies { compile 'com.github.LIPKKKK:VerticalMarqueeView:v1.0.3' }

how to use

 <com.lip.verticalmarqueeviewdemo.view.VerticalMarqueeView 
  android:id="@+id/lip_VerticalView" 
  android:layout_width="match_parent" 
  android:layout_height="0dp" 
  android:layout_weight="6" 
  app:VerticalMarqueeTextView_textColor = "#000"
  app:VerticalMarqueeTextView_textSize = "20sp" 
  app:VerticalMarqueeTextView_isRepeat = "true" 
  app:VerticalMarqueeTextView_startPoint = "0" 
  app:VerticalMarqueeTextView_direction = "1" 
  app:VerticalMarqueeTextView_speed = "20" />
```

VerticalMarqueeTextView_textColor : 文字顏色
VerticalMarqueeTextView_textSize : 文字大小
VerticalMarqueeTextView_isRepeat : 是否重復(fù)
VerticalMarqueeTextView_startPoint : 開始位置
VerticalMarqueeTextView_direction : 滾動(dòng)方向
VerticalMarqueeTextView_speed : 滾動(dòng)速度

具體使用我放到了git上,地址如下:
[LIPKKKK](https://github.com/LIPKKKK/VerticalMarqueeView)

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

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

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