实现方式1
//设置跑马灯效果,避免跑马灯效果失效
private void setMarqueeText() {
mTvTip.setEllipsize(TextUtils.TruncateAt.MARQUEE);
mTvTip.setSingleLine(true);
mTvTip.setSelected(true);
mTvTip.setFocusable(true);
mTvTip.setFocusableInTouchMode(true);
}
跑马灯相关属性
android:singleLine="true" //设置单行
android:scrollHorizontally="true"
android:ellipsize="marquee" //跑马灯
android:focusable="true" //获得焦点
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever" //无限循环
实现方式2:自定义跑马灯类
上面方式1能暂时实现跑马灯效果,但在多次点击事件之后容易失焦。而且在Android4.4上实现有短暂停顿。
MarqueeTextView
public class MarqueeTextView extends AppCompatTextView {
/**
* 滚动次数
*/
private int marqueeNum = -1;//-1为永久循环,大于0是循环次数。
public void setMarqueeNum(int marqueeNum) {
this.marqueeNum = marqueeNum;
}
public MarqueeTextView(Context context) {
super(context);
setAttr();
}
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setAttr();
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAttr();
}
/**
* 始终获取焦点
* 跑马灯在TextView处于焦点状态的时候才会滚动
*/
@Override
public boolean isFocused() {
return true;
}
/**
* 设置相关属性
*/
private void setAttr() {
this.setEllipsize(TextUtils.TruncateAt.MARQUEE);//设置跑马等效果
this.setMarqueeRepeatLimit(marqueeNum);//设置跑马灯重复次数
this.setSingleLine(true);//设置单行
}
}
实现方式3:自定义跑马灯类(自定义绘制控件)
MarqueeTextView
/**
* 自定义跑马灯
*/
public class MarqueeTextView extends View {
/**
* 界面刷新时间(ms)
*/
public static final int INVALIDATE_TIME = 12;
/**
* 每次移动的像素点(px)
*/
public static final int INVALIDATE_STEP = 2;
/**
* 是否第一次
*/
//private boolean isfirstCycle = true;
private String drawingText;
private TextPaint paint;
public boolean exitFlag;
private float textWidth;
private int posX = 0;
private float posY;
private int width;
private RectF rf;
private boolean hasInit;
private Handler mHandler = new Handler();
public MarqueeTextView(Context context) {
this(context, null);
}
public MarqueeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context, attrs, defStyle);
}
private void initView(Context context, AttributeSet attrs, int defStyle) {
paint = new TextPaint();
rf = new RectF(0, 0, 0, 0);
paint.setAntiAlias(true);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MarqueeTextView);
setTextSize(typedArray.getDimension(R.styleable.MarqueeTextView_mtvtextsize, 30f));
setTextColor(typedArray.getColor(R.styleable.MarqueeTextView_mtvtextcolor, Color.WHITE));
setText(typedArray.getString(R.styleable.MarqueeTextView_mtvtext));
}
public void setText(String text) {
this.drawingText = text;
posX = 0;
}
public void setTextSize(float dp) {
if (this.paint == null)
return;
this.paint.setTextSize(dp);
}
public void setTextColor(int color) {
if (this.paint == null)
return;
this.paint.setColor(color);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
if (drawingText != null) {
textWidth = paint.measureText(drawingText, 0, drawingText.length());
}
if (posX == 0 && !hasInit) {
rf.right = 0;
rf.bottom = MeasureSpec.getSize(heightMeasureSpec);
posX = 0;
posY = getTextDrawingBaseline(paint, rf);
hasInit = true;
}
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
if (getVisibility() != View.VISIBLE || TextUtils.isEmpty(drawingText)) {
return;
}
canvas.save();
canvas.drawText(drawingText, 0, drawingText.length(), posX, posY, paint);
canvas.restore();
}
private Runnable moveRun = new Runnable() {
@Override
public void run() {
//控制文本宽度大于控件宽度才进行滚动
if (width >= textWidth) {
posX = 0;
invalidate();
return;
}
//左移
posX -= INVALIDATE_STEP;
//当文字和空格完全移出屏幕,x值从1开始移动
if (posX < -1 * textWidth - paint.measureText("", 0, "".length())) {
posX = getWidth();
}
invalidate();
if (!exitFlag) {
mHandler.postDelayed(this, INVALIDATE_TIME);
return;
}
posX = 0;
}
};
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopMove();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == View.VISIBLE) {
startMove();
} else {
stopMove();
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
startMove();
} else {
stopMove();
}
}
private void stopMove() {
exitFlag = true;
if (mHandler == null)
return;
mHandler.removeCallbacksAndMessages(null);
}
public void startMove() {
exitFlag = false;
if (mHandler == null)
return;
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(moveRun, 0);
}
public void startMove(long delay) {
exitFlag = false;
if (mHandler == null)
return;
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(moveRun, delay);
}
/**
* 获取绘制文字的baseline
*
* @param paint
* @param targetRect
* @return
*/
public static float getTextDrawingBaseline(Paint paint, RectF targetRect) {
if (paint == null || targetRect == null) {
return 0;
}
Paint.FontMetrics fontMetric = paint.getFontMetrics();
return targetRect.top + (targetRect.height() - fontMetric.bottom + fontMetric.top) / 2.0f - fontMetric.top;
}
}
attrs.xml
使用
mTvTip3.setText("仓为品牌特卖仓储中心,库存较少,请尽a电视剧花短时间撒分散打发第三方大师傅大师傅奥德赛飞a");
mTvTip3.setTextColor(Color.BLACK);
mTvTip3.startMove(1000);
第三方跑马灯库
MarqueeView:可垂直跑、可水平跑的跑马灯。
MarqueeViewLibrary:一个很方便使用和扩展的跑马灯Library,通过提供不同的MarqueeFactory来定制不同的跑马灯View, 并且提供了常用类型的跑马灯效果:SimpleMarqueeView。
第三方跑马灯库MarqueeView的使用
implementation 'com.sunfusheng:MarqueeView:1.4.1'
XML
设置字符串列表数据,或者设置自定义的Model数据类型
MarqueeView marqueeView = (MarqueeView) findViewById(R.id.marqueeView);
List messages = new ArrayList<>();
messages.add("1. 大家好,我是孙福生。");
messages.add("2. 欢迎大家关注我哦!");
messages.add("3. GitHub帐号:sunfusheng");
messages.add("4. 新浪微博:孙福生微博");
messages.add("5. 个人博客:sunfusheng.com");
messages.add("6. 微信公众号:孙福生");
marqueeView.startWithList(messages);
// 或者设置自定义的Model数据类型
public class CustomModel implements IMarqueeItem {
@Override
public CharSequence marqueeMessage() {
return "...";
}
}
List messages = new ArrayList<>();
marqueeView.startWithList(messages);
// 在代码里设置自己的动画
marqueeView.startWithList(messages, R.anim.anim_bottom_in, R.anim.anim_top_out);
设置字符串数据
String message = "心中有阳光,脚底有力量!心中有阳光,脚底有力量!心中有阳光,脚底有力量!";
marqueeView.startWithText(message);
// 在代码里设置自己的动画
marqueeView.startWithText(message, R.anim.anim_bottom_in, R.anim.anim_top_out);
设置事件监听
marqueeView.setOnItemClickListener(new MarqueeView.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
Toast.makeText(getApplicationContext(), String.valueOf(marqueeView1.getPosition()) + ". " + textView.getText(), Toast.LENGTH_SHORT).show();
}
});
在 Activity 或 Fragment 中
@Override
public void onStart() {
super.onStart();
marqueeView.startFlipping();
}
@Override
public void onStop() {
super.onStop();
marqueeView.stopFlipping();
}
在 ListView 或 RecyclerView 的 Adapter 中
@Override
public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
holder.marqueeView.stopFlipping();
}