自定义滚动文本TextView

1.原生实现

要实现文本滚动,Android官方提供了简单的实现方式

android:singleLine="true"
android:ellipsize="marquee"

当然还有个先决条件,当前TextView有焦点(被用户点击选中)。
实际情况下,某些场景我们要求当前TextView在没有焦点情况下,也要让其滚动,
通过阅读源码我们发现,在设置了上述属性后,加上setSelect(true)也可以实现用户没有选中时实现滚动。

 

在实际开发中,还发现如下问题

1.当前页面有多个TextView,我们同时设置了setSelect(true)让其能同时滚动,但实际效果是有时候不知道什么原因会自动停止滚动,

然后也许你重复进入退出该页面,它又自动恢复滚动

2.滚动速度不能控制

 

2.自定义实现

所以考虑实现一个自定义的滚动解决这些问题,原理就是不断地在新的位置画设置的文本。实际的问题分解及解决思考如下

  1. 如何不断地执行 - 线程循环任务或者TimeTask、HandlerThread
  2. 画文本 - 调用drawText
  3. 速度控制 - 其实就是控制1中的循环任务间隔时间
  4. 最大滚动距离控制(滚出视线一定距离需要从头开始滚动)- 计算TextView的宽度,计算文本宽度,然后对比
  5. 当文本长度较小(小于我们TextView时)是否需要滚动 - 大部分情况下文本较短其实是不需要滚动的,解决方式同4去计算宽度
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

import java.util.Timer;
import java.util.TimerTask;

public class AlwaysMarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {

    private static final String TAG = "AlwaysMarqueeTextView";
    private int mOffsetX = 0;
    private Rect mRect;
    private String mText = "";
    private Timer mTimer;
    private TimerTask mTimerTask;
    private int mTextWidth;

    private int mSpeed = 2;
    private static final int PFS = 24;

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

    public AlwaysMarqueeTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mRect = new Rect();
        mTimer = new Timer();
        mTimerTask = new MyTimerTask();
        //更新帧率24
        mTimer.schedule(mTimerTask, 0, 1000 / PFS);
    }

    public void setMarqueeText(CharSequence text) {
        setText(text);
        mText = text.toString();
        TextPaint textPaint = getPaint();
        textPaint.getTextBounds(mText, 0, mText.length(), mRect);
        mTextWidth = mRect.width();
    }

    public void setMarqueeText(int resId) {
        setText(resId);
        mText = getText().toString();
        TextPaint textPaint = getPaint();
        textPaint.getTextBounds(mText, 0, mText.length(), mRect);
        mTextWidth = mRect.width();
    }


    private class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            if (mTextWidth < getWidth()){//文本长度小于View长度, 不进行滚动
                mOffsetX = 0;
                scrollTo(0, 0);//滚动到初始为止,如果不调用此方法,可能会导致文本被遮挡
                postInvalidate();
                return;
            }
            if (mOffsetX < - (mTextWidth / 2) - getPaddingEnd()){
                mOffsetX = getWidth();
            }
            mOffsetX -= mSpeed;
            postInvalidate();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mText = getText().toString();
        TextPaint textPaint = getPaint();
        textPaint.setColor(getCurrentTextColor());
        //获取文本区域大小,保存在mRect中。
        textPaint.getTextBounds(mText, 0, mText.length(), mRect);
        mRect.right = getWidth();//将绘画区域的右边设置为textview的宽度值
        float mTextCenterVerticalToBaseLine =
            ( - textPaint.ascent() + textPaint.descent()) / 2 - textPaint.descent();
        canvas.drawText(mText, mOffsetX, getHeight() / 2 + mTextCenterVerticalToBaseLine, textPaint);

    }

    /**
     * 视图移除时销毁任务和定时器
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.e(TAG, "killTimer");
        if (mTimerTask != null){
            mTimerTask.cancel();
            mTimerTask = null;
        }
        if (mTimer != null){
            mTimer.cancel();
            mTimer = null;
        }
    }

    public void setSpeed(int speed){
        this.mSpeed = speed;
    }
}

 

实现不算太难,关键是要弄懂原理,即使有问题,也容易快速定位解决

 

posted @ 2024-03-07 11:08  瓜的呱  阅读(126)  评论(0)    收藏  举报