展示类项目经常会用到文字跑马灯,包含水平方向和垂直方向,常见的需求还有控制播放速度等,网上找了很多相关的效果,但是不尽如人意,昨天找到了个MarqueeView,功能相对符合场景,便于配置,下面将把使用方法和问题点列出来,结尾附demo
先看效果:
1.自定义View(直接复制粘贴到自己项目里就好)
public class MarqueeView extends TextView {
//滚动方向
//不滚动
public static final int SCROLL_NO = 1;
//从下往上
public static final int SCROLL_BT = 2;
//从右往左
public static final int SCROLL_RL = 3;
//垂直滚动需要的数据
private float lineSpace;
private float verticalSpeed = 0.1f;
private List<String> textList = new ArrayList<>();
private StringBuilder textBuilder = new StringBuilder();
//水平滚动需要的数据
private float horizontalSpeed = 2f;
private Rect rect;
private Paint paint;
//默认不滚动
private int scrollType;
//每次更滚动的距离
private float scrollStep = 0f;
public MarqueeView(Context context) {
this(context, null);
}
public MarqueeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MarqueeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MarqueeView);
scrollType = array.getInt(R.styleable.MarqueeView_scrollType, SCROLL_NO);
int color = array.getColor(R.styleable.MarqueeView_textColor, 0x000000);
lineSpace = array.getInt(R.styleable.MarqueeView_lineSpace, 0);
array.recycle();
setSpeed(0, 1f);
setSpeed(1, 5f);
paint = getPaint();
paint.setColor(color);
rect = new Rect();
}
@Override
public void setTextColor(int color) {
super.setTextColor(color);
paint.setColor(color);
}
/**
* 设置滚动方向
*
* @param scrollType 滚动方向
*/
public void setScrollType(int scrollType) {
this.scrollType = scrollType;
invalidate();
}
/**
* 设置滚动速度
*
* @param type 滚动方向 0垂直 1水平
*/
public void setSpeed(int type, float speed) {
if (0 == type) {
verticalSpeed = speed;
} else {
horizontalSpeed = speed;
}
invalidate();
}
/**
* 设置行高
*
* @param lineSpace 行高
*/
public void setLineSpace(float lineSpace) {
this.lineSpace = lineSpace;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
String text = getText().toString();
if (!TextUtils.isEmpty(text) && scrollType == SCROLL_BT) {
//由下往上滚动需要测量高度
setTextList(widthMeasureSpec, text);
}
}
/**
* 根据TextView宽度和字体大小,计算显示的行数。
*
* @param widthMeasureSpec 测量模式
* @param text 文本
*/
private void setTextList(int widthMeasureSpec, String text) {
textList.clear();
float width = MeasureSpec.getSize(widthMeasureSpec);
float length = 0;
for (int i = 0; i < text.length(); i++) {
if (length <= width) {
textBuilder.append(text.charAt(i));
length += paint.measureText(text.substring(i, i + 1));
if (i == text.length() - 1) {
if (length <= width) {
textList.add(textBuilder.toString());
} else {
if (textBuilder.toString().length() == 1) {
//每行最多显示一个字
textList.add(text.substring(text.length() - 1));
} else {
//去掉最后一个字,否则最后一个字显示不完整
textList.add(textBuilder.toString().substring(0, textBuilder.toString().length() - 1));
//最后一个字单独一行
textList.add(text.substring(text.length() - 1));
}
}
}
} else {
if (textBuilder.toString().length() == 1) {
//每行最多显示一个字
textList.add(textBuilder.toString());
textBuilder.delete(0, textBuilder.length());
i--;
length = 0;
} else {
//去掉最后一个字,否则最后一个字显示不完整
textList.add(textBuilder.toString().substring(0, textBuilder.toString().length() - 1));
textBuilder.delete(0, textBuilder.length() - 1);
i--;
length = paint.measureText(text.substring(i, i + 1));
}
}
}
//清空textBuilder
textBuilder.delete(0, textBuilder.length());
}
@Override
public void onDraw(Canvas canvas) {
String text = getText().toString();
if (TextUtils.isEmpty(text)) {
super.onDraw(canvas);
return;
}
switch (scrollType) {
case SCROLL_NO:
super.onDraw(canvas);
break;
case SCROLL_BT:
//从下往上滚动,首次不显示文字,后续从下往上显示
float textSize = paint.getTextSize();
for (int i = 0; i < textList.size(); i++) {
float currentY = getHeight() + (i + 1) * textSize - scrollStep;
if (i > 0) {
currentY = currentY + i * lineSpace;
}
if (textList.size() > 1) {
canvas.drawText(textList.get(i), 0, currentY, paint);
} else {
canvas.drawText(textList.get(i), getWidth() / 2 - paint.measureText(text) / 2, currentY, paint);
}
}
scrollStep = scrollStep + verticalSpeed;
if (scrollStep >= getHeight() + textList.size() * textSize + (textList.size() - 1) * lineSpace) {
scrollStep = 0;
}
invalidate();
break;
case SCROLL_RL:
//从右向左滚动,首次不显示文字,后续每次往左偏移speed像素
paint.getTextBounds(text, 0, text.length(), rect);
int textWidth = rect.width();
int viewWidth = getWidth();
float currentX = viewWidth - scrollStep;
canvas.drawText(text, currentX, getHeight() / 2 + (paint.getFontMetrics().descent - paint.getFontMetrics().ascent) / 2 - paint.getFontMetrics().descent, paint);
scrollStep = scrollStep + horizontalSpeed;
if (scrollStep >= viewWidth + textWidth) {
scrollStep = 0;
}
invalidate();
break;
default:
break;
}
}
}
attrs.xml(res/values目录下)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MarqueeView">
<attr name="scrollType">
<enum name="no" value="1" />
<enum name="vertical" value="2" />
<enum name="horizontal" value="3" />
</attr>
<attr name="speedType">
<enum name="slow" value="4" />
<enum name="normal" value="5" />
<enum name="fast" value="6" />
<enum name="express" value="7" />
</attr>
<attr name="textColor" format="color" />
<!--行高,只对垂直滚动有效-->
<attr name="lineSpace" format="integer" />
</declare-styleable>
</resources>
布局文件(简单举例,详细看demo)
<com.hjly.marqueetextdemo.MarqueeView
android:id="@+id/marquee_horizontal"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#abc"
android:singleLine="true"
app:textColor="#333"
android:textSize="30sp" />
重点说明:
文本颜色,这个控件不能使用 android:textColor="@color/white"
而需要使用 app:textColor="@color/white"来设置文字颜色
否则,都会没有颜色,导致的结果就是没有文字,我之前自己整理demo的时候,代码都写完了,一运行就是没有效果,我就把原始的demo找出来,对比半天才发现是这个属性的问题
MainActivity(简单举例,详细看demo)
marquee_horizontal = findViewById(R.id.marquee_horizontal);
marquee_horizontal.setFocusable(true);
marquee_horizontal.setFocusableInTouchMode(true);
marquee_horizontal.setSpeed(1, (float) 5);
marquee_horizontal.setScrollType(SCROLL_RL);
marquee_horizontal.setText("水平跑马灯单行");
重要的属性就是设置滚动速度以及滚动方向
至此就实现了功能