模仿原生的自定义跑马灯

需要背景:

一个在 launcher 一直循环滚动的跑马效果

0. 循环滚动时,标题与标题的间隔能够自定义(原生的貌似不可以)

1. 当切屏或离开页面时,跑马灯停止更新

2. 虽然离开了跑马灯页面,但标题内容可能会被更新,因此回到其页面时,更新标题内容并判断是否需要滚动

3. 当标题过短,不用循环滚动显示时,标题居中

跑马灯实现效果.gif

CustomMarqueeView效果图.png

思路:

初始化

  • 判断是否需要滚动,通过标题的宽度与控件宽度比较,分为两种情况TYPE_NO_SCROLLTYPE_SCROLL

  • 标题循环滚动时,如果自定义标题与标题直接的间隔呢,mTitleWithSpace,就是添加了间隔的标题,可以通过定义 mSpaceCount来定义间隔

  • 计算标题向左侧滑动的最大距离mMaxScroll

 private void initMarqueeText() {
        if (!TextUtils.isEmpty(mTitleText)) {
            initPaint();
            //重置相关属性
            mWidth = getWidth();
            mHeight = getHeight() - getPaddingTop() - getPaddingBottom();
            mTextWidth = measureTextWidth(String.valueOf(mTitleText));
            //文字滚动判断
            if (mTextWidth > mWidth) {
                mTitleType = TYPE_SCROLL;
                mTitleWithSpace = mTitleText;
                for (int i = 0; i < mSpaceCount; i++) {
                    mTitleWithSpace += " ";
                }
                mDrawingText = mTitleWithSpace;
                mTitleCharList = mDrawingText.toCharArray();
                mMaxScroll = mTextWidth + mSpaceCount * oneSpaceWidth();
                mTextPaint.setTextAlign(Paint.Align.LEFT);
                startIndex = 0;
            } else {
                //不滚动
                mDrawingText = mTitleText;
                mTitleType = TYPE_NO_SCROLL;
            }
            mTextDefalutXShaft = 0;
        }
    }

onDraw

  • 根据不同的类型,draw 不同类型,其中mCenterNoScroll表示当标题能全部在控件上显示,而不用滚动时,标题是否居中

  • 核心是如何循环滚动,当右侧可以有多余的空间时,先判断这空间可以放多少个文字,然后会去拿标题数据的文字

 private void drawMarqueeText(Canvas canvas) {
     ...(省略)
        switch (mTitleType) {
            case TYPE_NO_SCROLL:
                LogUtils.i(TAG, "跑马灯标题不滚动");
                if (mDrawingText != null && !mDrawingText.equals("")) {
                    if (mCenterNoScroll) {
                        mTextPaint.setTextAlign(Paint.Align.CENTER);
                        canvas.drawText(mDrawingText, mWidth / 2, mHeight / 2, mTextPaint);
                    } else {
                        mTextPaint.setTextAlign(Paint.Align.LEFT);
                        canvas.drawText(mDrawingText, 0, mHeight / 2, mTextPaint);
                    }
                }
                break;
            case TYPE_SCROLL:
                if (!TextUtils.isEmpty(mDrawingText)) {
                    if (isShown()) {
                        canvas.drawText(mDrawingText, mTextDefalutXShaft, mHeight / 2, mTextPaint);//从坐标0开始绘制
                        mTextDefalutXShaft = mTextDefalutXShaft - mSpeed;
                    }
                      //当达到条件后,暂停一下,然后从0开始绘制
                    if (mMaxScroll < Math.abs(mTextDefalutXShaft)) {
                        mDrawingText = mTitleWithSpace;
                        mTextDefalutXShaft = 0;
                        startIndex = 0;
                        postInvalidateDelayed(mPauseTime);
                    } else {
                        // 控件总宽度 - 可见文字宽度(文字宽度-文字向x轴原点左边滑动距离)= 右边剩余无文字的宽度
                        //右侧的剩余空间可以填充的字数
                        int rightTitleCount = (int) ((mWidth - (mTextWidth + mTextDefalutXShaft)) / oneSpaceWidth());
                        if (rightTitleCount < 0) {
                            postInvalidateDelayed(30);
                            return;
                        }
                        for (int i = startIndex; i < rightTitleCount - mSpaceCount; i++) {
                            if (i < mTitleCharList.length) {//拿titleText中的文字
                                mDrawingText = mDrawingText + mTitleCharList[i];
                                startIndex++;
                            }
                        }
                        postInvalidateDelayed(30);
                    }

                } else {
                    LogUtils.d(TAG, "drawMarqueeText mMarqueeText是空的");
                }
                break;
        }
    }

暂停滚动

  //暂停滚动
    public void stopScroll() {
        isRunning = false;
    }

此时如果跑马灯在滚动时,走到onDraw ,生效

 private void drawMarqueeText(Canvas canvas) {
        if (!isRunning) {
            LogUtils.i(TAG, "drawMarqueeText: pause");
            if (isShown()) {
                canvas.drawText(mDrawingText, 0, mHeight / 2, mTextPaint);//从坐标0开始绘制
            }
            return;
        }
   ...(省略)
}

继续、内容更新

 //设置滚动的文字
    public void setScrollText(String text) {
        if (TextUtils.isEmpty(text) || text.equals(mTitleText)) {
            return;
        }
        mTitleText = text;
        boolean isScroll = mTitleType == TYPE_SCROLL;
        if (mTitleType != TYPE_IDLE) {//idle 状态 onSizeChanged 去初始化
            initMarqueeText();
        }
        if (isRunning && !isScroll) {//不是需要暂停并且没有滚动的情况下,调用
            postInvalidate();
        }
    }
  //继续滚动
    public void startScroll() {
        if (!isRunning) {
            isRunning = true;
            postInvalidate();
        }
    }

总结

这个自定义跑马灯最核心的,是如何循环滚动,计算 draw 的开始的位置与文字;
而标题内容更新、继续滚动等情况需求考虑是否重复加载。
如果想看更多代码细节,可以看 Demo

你可能感兴趣的:(模仿原生的自定义跑马灯)