Android 滚动显示的 TextView

ScrollTextView

新博客已经迁移至:Dkaishu的博客 ,http://dkaishu.com/
很多 App 中都有滚动展示文字需求,特别适合用来展示简短的通知和广告等内容,不多说,先简单看下效果,
这里写图片描述

具体的 UI 可以自己定义,我已经其开原到 github :https://github.com/Dkaishu/ScrollTextView,并可以在 gradle 中引用到工程,只需一行代码。如何使用见上面链接。推荐结合源码阅读下面内容。

实现思路

滚动文字的实现思路很多种,我觉得简单易实现的的大致可分为两种:一是,类似常见 Banner 的思路,实现 banner,将TextView 添加到ViewGroup中,这种方式扩展性强,如果滚动显示的不仅仅是文字,还有其他View时,可考虑,淘宝首页的滚动条应该是这样实现的;二、自定义View继承 TextView 或 View 等,这种方式代码量会小很多,性能好很多,使用也简单很多,当需求单一时,优先考虑。此 ScrollTextView 是基于第二种思路实现,下面是几个关键点。

支持的属性

因为应用场景单一,所以不必考虑过多属性,核心的几个:文字颜色大小、背景、滚动速度、展示时间等;其他:不同条目的点击事件监听,当前显示文字的监听等。这样看来,我们直接继承 View 就完全可以满足需求了。

    /**
     * 默认文字颜色
     */
    private static final int DEFAULT_TEXT_COLOR = Color.BLACK;

    /**
     * 默认文字大小(单位sp)
     */
    private static final int DEFAULT_TEXT_SIZE = 16;

    /**
     * 单行模式
     */
    private static final boolean SINGLE_LINE = true;

    /**
     * 单行显示时,默认带省略号(单行模式下才有效)
     */
    private static final boolean ELLIPSIS = true;

    /**
     * 默认文字滚动时间(滚动控制速度)
     */
    private static final long DEFAULT_SCROLL_TIME = 500;

    /**
     * 默认文字切换间隔时间
     */
    private static final long DEFAULT_SPAN_TIME = 3000;

    /**
     * 文字滚动时间,默认500ms
     */
    private long scrollTime = DEFAULT_SCROLL_TIME;

    /**
     * 文字切换间隔时间,默认3000ms
     */
    private long spanTime = DEFAULT_SPAN_TIME;

    /**
     * 是否单行模式
     */
    private boolean isSingleLine;

    /**
     * 单行显示下是否自带省略号
     */
    private boolean isEllipsis;
自定义 View

构造方法:

  public ScrollTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScrollTextLayout, defStyleAttr, 0);
        int textColor = DEFAULT_TEXT_COLOR;
        float textSize = sp2px(context, DEFAULT_TEXT_SIZE);
        if (typedArray != null) {
            textColor = typedArray.getColor(R.styleable.ScrollTextLayout_textColor, textColor);
            textSize = typedArray.getDimension(R.styleable.ScrollTextLayout_textSize, textSize);
            isSingleLine = typedArray.getBoolean(R.styleable.ScrollTextLayout_singleLine, SINGLE_LINE);
            isEllipsis = typedArray.getBoolean(R.styleable.ScrollTextLayout_ellipsis, ELLIPSIS);
            typedArray.recycle();
        }

        mPaint = new Paint();
        mPaint.setColor(textColor);
        mPaint.setTextSize(textSize);
        mPaint.setAntiAlias(true);

        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        mTextHeight = fontMetrics.bottom - fontMetrics.top;
        mTextOffsetY = -fontMetrics.top;

        mIndexMap = new HashMap<>();
        mTextInfos = new LinkedList<>();
        mEllipsisTextInfos = new ArrayList<>();

        setOnClickListener(this);
    }

重写 onMeasure 与 dispatchDraw

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);

        int textMaxWidth = 0;
        if (mContents != null && mContents.size() > 0) {
            textMaxWidth = textTypeSetting(measuredWidth - getPaddingLeft() - getPaddingRight(), mContents);
            mCurrentTextInfos = mTextInfos.poll();
            mTextInfos.offer(mCurrentTextInfos);
        }
        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            measuredWidth = textMaxWidth + getPaddingLeft() + getPaddingRight();
        }
        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            measuredHeight = (int) (mTextHeight + getPaddingBottom() + getPaddingTop());
        }
        setMeasuredDimension(measuredWidth, measuredHeight);
        startTextScroll();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mCurrentTextInfos != null && mCurrentTextInfos.size() > 0) {
            for (TextInfo textInfo : mCurrentTextInfos) {
                canvas.drawText(textInfo.text,
                        textInfo.x + getPaddingLeft(),
                        textInfo.y + getPaddingTop() + mTop,
                        mPaint);
            }
        }
        if (mTextInfos.size() > 1) {
            List<TextInfo> nextTextInfos = mTextInfos.peek();
            if (nextTextInfos != null && nextTextInfos.size() > 0) {
                for (TextInfo textInfo : nextTextInfos) {
                    canvas.drawText(textInfo.text, textInfo.x + getPaddingLeft(), textInfo.y + getPaddingTop() + mTop
                            + mTextHeight + getPaddingTop() + getPaddingBottom(), mPaint);
                }
            }
        }
    }

hander.postDelayed 控制时间

    Handler mHandler = new Handler();

    Runnable mRunnable = new Runnable() {

        @Override
        public void run() {
            if (mTextInfos.size() > 1) {
                mValueAnimator = ValueAnimator.ofFloat(0.0f, -1.0f);
                mValueAnimator.setDuration(scrollTime);
                final OnScrollListener sl = mIndexMap.get(mCurrentTextInfos).getScrollListener();
                mValueAnimator.addListener(new Animator.AnimatorListener() {

                    @Override
                    public void onAnimationStart(Animator animation) {
                        if (sl != null) sl.onScrollStart(mCurrentTextInfos);
                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mTop = 0;
                        mCurrentTextInfos = mTextInfos.poll();
                        mTextInfos.offer(mCurrentTextInfos);
                        if (sl != null) sl.onScrollEnd(mCurrentTextInfos);
                        startTextScroll();
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        mTop = 0;
                    }
                });
                mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (Float) animation.getAnimatedValue();
                        mTop = (int) (value * (mTextHeight + getPaddingTop() + getPaddingBottom()));
                        invalidate();
                    }

                });
                mValueAnimator.start();
            }
        }
    };

难点:文字排版

    private int textTypeSetting(float maxParentWidth, List<String> list) {
        // 清空数据及初始化数据
        mTextInfos.clear();
        mIndexMap.clear();
        mEllipsisTextInfos.clear();
        mEllipsisTextWidth = 0f;
        // 初始化省略号
        if (isSingleLine && isEllipsis) {
            String ellipsisText = "...";
            for (int i = 0; i < ellipsisText.length(); i++) {
                char ch = ellipsisText.charAt(i);
                float[] widths = new float[1];
                String srt = String.valueOf(ch);
                mPaint.getTextWidths(srt, widths);
                TextInfo textInfo = new TextInfo();
                textInfo.text = srt;
                textInfo.x = mEllipsisTextWidth;
                textInfo.y = mTextOffsetY;
                mEllipsisTextInfos.add(textInfo);
                mEllipsisTextWidth += widths[0];
            }
        }
        // 文字排版
        float maxWidth = 0;
        // 文字排版最大宽度
        float tempMaxWidth = 0f;
        int index = 0;
        for (String text : list) {
            if (isNullOrEmpty(text)) {
                continue;
            }
            // 排版文字当前的宽度
            float textWidth = 0;
            // 文字信息集合
            List<TextInfo> textInfos = new ArrayList<TextInfo>();
            if (isSingleLine) {
                // 临时文字信息集合
                List<TextInfo> tempTextInfos = new ArrayList<TextInfo>();
                // 单行排不下
                boolean isLess = false;
                // 省略号的起始位置
                float ellipsisStartX = 0;
                for (int j = 0; j < text.length(); j++) {
                    char ch = text.charAt(j);
                    float[] widths = new float[1];
                    String srt = String.valueOf(ch);
                    mPaint.getTextWidths(srt, widths);
                    TextInfo textInfo = new TextInfo();
                    textInfo.text = srt;
                    textInfo.x = textWidth;
                    textInfo.y = mTextOffsetY;
                    textWidth += widths[0];
                    if (textWidth <= maxParentWidth - mEllipsisTextWidth) // 当排版的宽度小于等于最大宽度去除省略号长度时
                    {
                        textInfos.add(textInfo);
                        ellipsisStartX = textWidth;
                    } else if (textWidth <= maxParentWidth) // 当排版宽度小于最大宽度时
                    {
                        tempTextInfos.add(textInfo);
                    } else
                    // 最大宽度排版不下
                    {
                        isLess = true;
                        break;
                    }
                }
                if (isLess) {
                    tempMaxWidth = maxParentWidth;
                    for (TextInfo ellipsisTextInfo : mEllipsisTextInfos) {
                        TextInfo textInfo = new TextInfo();
                        textInfo.text = ellipsisTextInfo.text;
                        textInfo.x = (ellipsisTextInfo.x + ellipsisStartX);
                        textInfo.y = ellipsisTextInfo.y;
                        textInfos.add(textInfo);
                    }
                } else {
                    tempMaxWidth = textWidth;
                    textInfos.addAll(tempTextInfos);
                }
                if (tempMaxWidth > maxWidth) {
                    maxWidth = tempMaxWidth;
                }
                mTextInfos.offer(textInfos);
                if (mScrollClickListeners != null && mScrollClickListeners.size() > index) {
                    mIndexMap.put(textInfos, new ListenersInfo(mScrollClickListeners.get(index), null));
                }
                if (mScrollListeners != null && mScrollListeners.size() > index) {
                    mIndexMap.get(textInfos).setScrollListener(mScrollListeners.get(index));
                }
                index++;
            } else {
                for (int j = 0; j < text.length(); j++) {
                    char ch = text.charAt(j);
                    float[] widths = new float[1];
                    String srt = String.valueOf(ch);
                    mPaint.getTextWidths(srt, widths);
                    TextInfo textInfo = new TextInfo();
                    textInfo.text = srt;
                    textInfo.x = textWidth;
                    textInfo.y = mTextOffsetY;
                    textWidth += widths[0];
                    if (textWidth > maxParentWidth) // 当排版宽度小于最大宽度时
                    {
                        tempMaxWidth = maxParentWidth;
                        mTextInfos.offer(textInfos);
                        if (mScrollClickListeners != null && mScrollClickListeners.size() > index) {
                            mIndexMap.put(textInfos, new ListenersInfo(mScrollClickListeners.get(index), null));
                        }
                        if (mScrollListeners != null && mScrollListeners.size() > index) {
                            mIndexMap.get(textInfos).setScrollListener(mScrollListeners.get(index));
                        }

                        textInfos = new ArrayList<TextInfo>();
                        textInfo.x = 0;
                        textInfo.y = mTextOffsetY;
                        textWidth = widths[0];
                    }
                    textInfos.add(textInfo);
                }
                if (textWidth > tempMaxWidth) {

                    tempMaxWidth = textWidth;
                }
                mTextInfos.offer(textInfos);
                if (tempMaxWidth > maxWidth) {
                    maxWidth = tempMaxWidth;
                }
                if (mScrollClickListeners != null && mScrollClickListeners.size() > index) {
                    mIndexMap.put(textInfos, new ListenersInfo(mScrollClickListeners.get(index), null));
                }
                if (mScrollListeners != null && mScrollListeners.size() > index) {
                    mIndexMap.get(textInfos).setScrollListener(mScrollListeners.get(index));
                }
                index++;
            }
        }
        return (int) maxWidth;
    }

滚动文字的监听

    /**
     * 描述:内容滚动动画开始与结束的监听事件
     */
    public interface OnScrollListener {
        /**
         * @param passedTextInfos 动画开始前显示的文字
         */
        void onScrollStart(List<TextInfo> passedTextInfos);

        /**
         * @param incommingTextInfos
         */
        void onScrollEnd(List<TextInfo> incommingTextInfos);
    }

设置数据源

    /**
     * @param list                 滚动内容集合
     * @param scrollClickListeners 滚动内容的点击监听集合 @nullable
     * @param scrollListeners      滚动动画开始与停止监听集合 @nullable
     */
    public void setTextContent(List<String> list, List<OnScrollClickListener> scrollClickListeners
            , List<OnScrollListener> scrollListeners) {
        this.mContents = list;
        this.mScrollClickListeners = scrollClickListeners;
        this.mScrollListeners = scrollListeners;
        requestLayout();
        invalidate();
    }

使用说明

详见 https://github.com/Dkaishu/ScrollTextView

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值