自定义滚动文本TextView
1.原生实现
要实现文本滚动,Android官方提供了简单的实现方式
android:singleLine="true"
android:ellipsize="marquee"
当然还有个先决条件,当前TextView有焦点(被用户点击选中)。
实际情况下,某些场景我们要求当前TextView在没有焦点情况下,也要让其滚动,
通过阅读源码我们发现,在设置了上述属性后,加上setSelect(true)也可以实现用户没有选中时实现滚动。
在实际开发中,还发现如下问题
1.当前页面有多个TextView,我们同时设置了setSelect(true)让其能同时滚动,但实际效果是有时候不知道什么原因会自动停止滚动,
然后也许你重复进入退出该页面,它又自动恢复滚动
2.滚动速度不能控制
2.自定义实现
所以考虑实现一个自定义的滚动解决这些问题,原理就是不断地在新的位置画设置的文本。实际的问题分解及解决思考如下
- 如何不断地执行 - 线程循环任务或者TimeTask、HandlerThread
- 画文本 - 调用drawText
- 速度控制 - 其实就是控制1中的循环任务间隔时间
- 最大滚动距离控制(滚出视线一定距离需要从头开始滚动)- 计算TextView的宽度,计算文本宽度,然后对比
- 当文本长度较小(小于我们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; } }
实现不算太难,关键是要弄懂原理,即使有问题,也容易快速定位解决
浙公网安备 33010602011771号