思路:
开启定时器刷新绘制文本的位置即可达到效果。
步骤1:新建ScrollTextView类继承自TextView。代码如下:
public class ScrollTextView extends TextView {
private static final String TAG = "ScrollTextView";
private String mText = "蒹葭苍苍,白露为霜。所谓伊人,在水一方。";
private int mOffsetX = 0;
private Rect mRect;
private Timer mTimer;
private TimerTask mTimerTask;
/**
* 速度,负数左移,正数右移。
*/
private int mSpeed = -10;
private static final int PFS = 24;
public ScrollTextView(Context context) {
this(context, null);
}
public ScrollTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mTimer = new Timer();
mTimerTask = new MyTimerTask();
mTimer.schedule(mTimerTask, 0, 1000 / 24);
}
private class MyTimerTask extends TimerTask {
@Override
public void run() {
//如果View能容下所有文字,直接返回
if (mRect.right < getWidth()){
return;
}
if (mOffsetX < - mRect.right - getPaddingEnd()){
//左移时的情况
mOffsetX = getPaddingStart();
} else if (mOffsetX > getPaddingStart()){
//右移时的情况
mOffsetX = - mRect.right;
}
mOffsetX += mSpeed;
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取文本区域大小,保存在mRect中。
getPaint().getTextBounds(mText, 0, mText.length(), mRect);
if (mRect.right < getWidth()){
canvas.drawText(mText, 0, getHeight() / 2, getPaint());
}else {
canvas.drawText(mText, mOffsetX, getHeight() / 2, getPaint());
}
}
/**
* 视图移除时销毁任务和定时器
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.e(TAG, "killTimer");
if (mTimerTask != null){
mTimerTask.cancel();
mTimerTask = null;
}
if (mTimer != null){
mTimer.cancel();
mTimer = null;
}
}
}
完成上面代码可以的到如下效果:
以上是硬编码文字,如果尝试在XML布局或代码设置文字,将会显示的一塌糊涂:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ScrollTextView textView = findViewById(R.id.scrollTextView);
textView.setText("开关磁阻电机的优缺点及使用");
}
步骤2:需要对onDraw()做一些更改,如下:
@Override
protected void onDraw(Canvas canvas) {
//此处去掉了super.onDraw(Canvas canvas);
mText = getText().toString();
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
//获取文本区域大小,保存在mRect中。
textPaint.getTextBounds(mText, 0, mText.length(), mRect);
if (mRect.right < getWidth()){
canvas.drawText(mText, 0, getHeight() / 2, textPaint);
}else {
canvas.drawText(mText, mOffsetX, getHeight() / 2, textPaint);
}
}
一切都很正常,把字体调大再看看,发现并没有居中显示,而是稍稍偏上。
问题就在于:
canvas.drawText(mText, mOffsetX, getHeight() / 2, textPaint);
第三个参数,垂直方向的中点和文字的基准点并不重合。
图片引用自 android canvas drawText()文字居中 感谢原作,侵权立删。
需做如下计算:
float mTextCenterVerticalToBaseLine =
( - textPaint.ascent() + textPaint.descent()) / 2 - textPaint.descent();
这样就能文字居中了:
canvas.drawText(mText, mOffsetX, getHeight() / 2 + mTextCenterVerticalToBaseLine, textPaint);
别忘了在布局XML中设置单行显示,因为没有重写测量方法,布局使用wrap_content时得到的并不是我们想要的尺寸。
android:maxLines="1"
到此就完成了。
最后附上完整代码:
自定义的ScrollTextView类:
public class ScrollTextView extends TextView {
private static final String TAG = "ScrollTextView";
private String mText = "蒹葭苍苍,白露为霜。所谓伊人,在水一方。";
private int mOffsetX = 0;
private Rect mRect;
private Timer mTimer;
private TimerTask mTimerTask;
/**
* 速度,负数左移,正数右移。
*/
private int mSpeed = -10;
private static final int PFS = 24;
public ScrollTextView(Context context) {
this(context, null);
}
public ScrollTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mTimer = new Timer();
mTimerTask = new MyTimerTask();
//更新帧率24
mTimer.schedule(mTimerTask, 0, 1000 / PFS);
}
private class MyTimerTask extends TimerTask {
@Override
public void run() {
//如果View能容下所有文字,直接返回
if (mRect.right < getWidth()){
return;
}
if (mOffsetX < - mRect.right - getPaddingEnd()){
//左移时的情况
mOffsetX = getPaddingStart();
} else if (mOffsetX > getPaddingStart()){
//右移时的情况
mOffsetX = - mRect.right;
}
mOffsetX += mSpeed;
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
//此处去掉了super.onDraw(Canvas canvas);
mText = getText().toString();
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
//获取文本区域大小,保存在mRect中。
textPaint.getTextBounds(mText, 0, mText.length(), mRect);
float mTextCenterVerticalToBaseLine =
( - textPaint.ascent() + textPaint.descent()) / 2 - textPaint.descent();
if (mRect.right < getWidth()){
canvas.drawText(mText, 0, getHeight() / 2 + mTextCenterVerticalToBaseLine, textPaint);
}else {
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;
}
}
MainActivity:
public class MainActivity extends Activity {
String text1 = "桑之未落,其叶沃若。于嗟鸠兮,无食桑葚;\n" +
"于嗟女兮,无与士耽。士之耽兮,犹可说也;\n" +
"女之耽兮,不可说也。";
String text2 = "桑之落矣,其黄而陨。自我徂尔,三岁食贫。\n" +
"淇水汤汤,渐车帷裳。女也不爽,士贰其行。\n" +
"士也罔极,二三其德。";
String text3 = "三岁为妇,靡室劳矣;夙兴夜寐,靡有朝矣。\n" +
"言既遂矣,至于暴矣。兄弟不知,咥其笑矣。\n" +
"静言思之,躬自悼矣。";
String text4 = "及尔偕老,老使我怨。淇则有岸,隰则有泮。\n" +
"总角之宴,言笑晏晏。信誓旦旦,不思其反。\n" +
"反是不思,亦已焉哉!";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ScrollTextView textView1 = findViewById(R.id.scrollTextView1);
textView1.setText(text1);
textView1.setSpeed(-5);
ScrollTextView textView2 = findViewById(R.id.scrollTextView2);
textView2.setText(text2);
textView2.setSpeed(10);
ScrollTextView textView3 = findViewById(R.id.scrollTextView3);
textView3.setText(text3);
textView3.setSpeed(-15);
ScrollTextView textView4 = findViewById(R.id.scrollTextView4);
textView4.setText(text4);
textView4.setSpeed(20);
}
}
布局文件activity_main.xml:
工程文件:
https://github.com/peoples-mountain-peoples-sea/ScrollText