美文网首页具体自定义控件工作生活
模仿原生的自定义跑马灯

模仿原生的自定义跑马灯

作者: RenHaiRenWjc | 来源:发表于2019-06-30 11:40 被阅读11次

    需要背景:

    一个在 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

    相关文章

      网友评论

        本文标题:模仿原生的自定义跑马灯

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