Android 竖滚加横滚的跑马灯(自定义 MarqueeView )
一.效果图
没错,就是要做一个能竖屏滚动+横屏滚动的花式跑马灯。
二.思路
竖屏滚动首先想到的肯定是ViewFlipper,那么竖屏滚动的ViewFlipper加上自定义的横屏滚动跑马灯岂不是就大功告成。来,开始操作:
1.自定义一个横向滚动的TexHorizontalView:
具体步骤:
a.计算需要展示文字的宽度textWidth和控件的宽度viewWitdh,判断文字的宽度textWidth是否大于控件控件宽度viewWitdh,如果大于则需要滚动。
b.设置值动画同时计算应到滚动的坐标,然后根据计算出的坐标用postInvalidate进行重绘。
c.postInvalidate会调用onDraw方法,然后在onDraw中根据坐标进行drawText绘制文本。
d.当值动画完毕设置横向滚动完毕事件的回调给外部使用。
2.自定义竖屏滚动的MarqueeView:
具体步骤:
a.自定义MarqueeView继承ViewFlipper,并在MarqueeView中addView添加对应个数的TexHorizontalView。
b.开启TexHorizontalView的横向滚动;当MarqueeView中的子TexHorizontalView横向滚动完毕,在TexHorizontalView滚动完毕的回调中开启MarqueeView的竖向滚动,从而展示下一个子TexHorizontalView。
c.当MarqueeView的竖向滚动动画完毕后,在动画完毕的监听开启当前TexHorizontalView开始横滚。
三.代码
1.TextHorizontalView
public class TextHorizontalView extends View {
/**
* 文字颜色,默认黑色
*/
private int mTextColor = Color.BLACK;
/**
* 文字大小
*/
private int mTextSize = 12;
/**
* 文本的x坐标
*/
private float mXLocation = 0;
/**
* 滚动每个字的时间
*/
private int mScrollSpeedTime = 300;
/**
* 画笔
*/
private TextPaint mPaint;
private Rect mRect;
/**
* 内容
*/
private String mContent;
/**
* 文字高度
*/
private float mTextHeight;
/**
* 值动画,配合postInvalidate刷新界面
*/
private ValueAnimator verticalSwitchAnimator;
/**
* 开启动画
*/
private boolean startRoll;
/**
* 滚动完毕的监听
*/
public interface OnTextScrollFinishListener {
void scrollFinish();
}
private OnTextScrollFinishListener onTextScrollFinishListener;
public void setOnTextScrollFinishListener(OnTextScrollFinishListener onTextScrollFinishListener) {
this.onTextScrollFinishListener = onTextScrollFinishListener;
}
public TextHorizontalView(Context context) {
this(context, null);
}
public TextHorizontalView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextHorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
initPaint();
}
@SuppressLint("RestrictedApi")
private void initAttrs(AttributeSet attrs) {
TintTypedArray tta = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.TextHorizontalView);
mTextColor = tta.getColor(R.styleable.TextHorizontalView_text_color, mTextColor);
mTextSize = tta.getInt(R.styleable.TextHorizontalView_text_size, mTextSize);
mScrollSpeedTime = tta.getInt(R.styleable.TextHorizontalView_text_scroll_speed_time, mScrollSpeedTime);
tta.recycle();
}
private void initPaint() {
mRect = new Rect();
//初始化文本画笔
mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
//文字颜色值,可以不设定
mPaint.setColor(mTextColor);
//文字大小
mPaint.setTextSize(mTextSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (startRoll) {
startRoll = false;
operateScroll();
}
//把文字画出来
if (mContent != null) {
getContentWidth(mContent);
canvas.drawText(mContent, mXLocation, getHeight() / 2 + mTextHeight / 2, mPaint);
}
}
/**
* 开始滚动
* postInvalidate调用onDraw中的operateScroll开始绘制
*/
public void startRoll() {
startRoll = true;
postInvalidate();
}
/**
* 停止滚动,将值动画cancel
*/
public void stopScroll() {
if (verticalSwitchAnimator != null) {
verticalSwitchAnimator.cancel();
}
}
/**
* 重置视图
*/
public void resetVew() {
mXLocation = 0;
postInvalidate();
}
private void operateScroll() {
final int moreWidth = (int) (getContentWidth(mContent) - getMeasuredWidth()) + mTextSize;
//文字是否超出
if (moreWidth <= 0) {
if (onTextScrollFinishListener != null) {
onTextScrollFinishListener.scrollFinish();
}
} else {
verticalSwitchAnimator = ValueAnimator.ofFloat(0, 1);
verticalSwitchAnimator.setDuration(mScrollSpeedTime * moreWidth / mTextSize);
verticalSwitchAnimator.setInterpolator(new LinearInterpolator());
verticalSwitchAnimator.start();
verticalSwitchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mXLocation = -value * moreWidth;
postInvalidate();
}
});
verticalSwitchAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (onTextScrollFinishListener != null) {
onTextScrollFinishListener.scrollFinish();
}
}
});
}
}
private float getContentWidth(String mContent) {
if (TextUtils.isEmpty(mContent)) {
return 0;
}
if (mRect == null) {
mRect = new Rect();
}
mPaint.getTextBounds(mContent, 0, mContent.length(), mRect);
mTextHeight = getContentHeight();
return mRect.width();
}
private float getContentHeight() {
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
return Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2;
}
/**
* 设置文字颜色
*/
public void setTextColor(int mTextColor) {
if (mTextColor != 0) {
this.mTextColor = mTextColor;
//文字颜色值,可以不设定
mPaint.setColor(mTextColor);
}
}
/**
* 设置文字大小
*/
public void setTextSize(int mTextSize) {
if (mTextSize > 0) {
this.mTextSize = mTextSize;
mPaint.setTextSize(mTextSize);
}
}
/**
* 设置每个字的滚动时间
*/
public void setTextSpeedTime(int mScrollSpeedTime) {
this.mScrollSpeedTime = mScrollSpeedTime;
}
/**
* 设置滚动的条目内容 字符串形式的
*/
public void setData(String mContent) {
if (TextUtils.isEmpty(mContent)) {
return;
}
this.mContent = mContent;
}
}
2.MarqueeView
public class MarqueeView extends ViewFlipper {
/**
* 当前横向滚动文本的索引
*/
private int mCurrentIndex;
/**
* 横向滚动文本的宽
*/
private int mTextWith = 500;
/**
* 横向滚动文本的高
*/
private int mTextHeight = 70;
/**
* 横向滚动文本字体大小
*/
private int mTextSize = 40;
/**
* 文本横向滚动的速度
*/
private int mTextScrollSpeed = 200;
/**
* 竖向滚动的时间
*/
private int mSwitchTime = 1000;
/**
* 用来做延迟操作
*/
private Handler handler = new Handler(Looper.getMainLooper());
public MarqueeView(Context context) {
this(context, null);
}
public MarqueeView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
@SuppressLint("RestrictedApi")
private void init(AttributeSet attrs) {
TintTypedArray typedArray = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.MarqueeView);
mTextScrollSpeed = typedArray.getInt(R.styleable.MarqueeView_horizontal_text_speed, mTextScrollSpeed);
mTextSize = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_size, mTextSize);
mTextWith = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_width, mTextWith);
mTextHeight = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_height, mTextHeight);
mSwitchTime = typedArray.getInt(R.styleable.MarqueeView_horizontal_text_switch_time, mSwitchTime);
typedArray.recycle();
}
public void setDataAndScroll(List<String> textList, List<Integer> colorList) {
if (textList == null || textList.size() == 0 || colorList == null || colorList.size() == 0) {
return;
}
//添加横向滚动view到当前控件
addTextView(textList, colorList);
//初始化监听
initListener(textList.size());
//开启滚动第一行
startScroll(0, textList.size());
}
private void addTextView(final List<String> textList, List<Integer> colorList) {
if (textList.size() == 1) {
textList.addAll(textList);
}
//回收资源
recyclerResource();
removeAllViews();
for (int i = 0; i < textList.size(); i++) {
final TextHorizontalView textHorizontalView = new TextHorizontalView(getContext());
LayoutParams params = new LayoutParams(mTextWith, mTextHeight);
//设置速度
textHorizontalView.setTextSpeedTime(mTextScrollSpeed);
//设置文字大小
textHorizontalView.setTextSize(mTextSize);
textHorizontalView.setData(textList.get(i));
if (colorList.size() == 1) {
textHorizontalView.setTextColor(getResources().getColor(colorList.get(0)));
} else if (colorList.size() > i) {
textHorizontalView.setTextColor(getResources().getColor(colorList.get(i)));
}
final int finalI = i;
textHorizontalView.setOnTextScrollFinishListener(new TextHorizontalView.OnTextScrollFinishListener() {
@Override
public void scrollFinish() {
int position = finalI + 1;
if (position == textList.size()) {
position = 0;
}
mCurrentIndex = position;
//横向滚动完毕,延时mSwitchTime开始下一个竖向滚动
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (isFlipping()) {
return;
}
if (finalI == 0) {
stopFlipping();
startFlipping();
}
showNext();
}
}, mSwitchTime);
}
});
textHorizontalView.setLayoutParams(params);
addView(textHorizontalView);
}
}
private void initListener(final int size) {
//设置动画
Animation inAnim = AnimationUtils.loadAnimation(getContext(), R.anim.anim_come_in);
setInAnimation(inAnim);
Animation outAnim = AnimationUtils.loadAnimation(getContext(), R.anim.anim_get_out);
setOutAnimation(outAnim);
getInAnimation().setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
//竖向滚动完毕,开始横滚
startScroll(mCurrentIndex, size);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void startScroll(int index, int size) {
//停止竖滚,开始横向滚动
stopFlipping();
int lastIndex;
if (index == 0) {
lastIndex = size - 1;
} else {
lastIndex = index - 1;
}
//上次的重置
TextHorizontalView horizontalLastView = (TextHorizontalView) getChildAt(lastIndex);
horizontalLastView.resetVew();
//当前滚动
TextHorizontalView horizontalView = (TextHorizontalView) getChildAt(index);
horizontalView.startRoll();
}
private void stopScroll() {
//停止竖滚,开始横向滚动
stopFlipping();
//当前滚动
TextHorizontalView horizontalView = (TextHorizontalView) getChildAt(mCurrentIndex);
if (horizontalView != null) {
horizontalView.stopScroll();
}
}
private void recyclerResource() {
//回收资源
stopScroll();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
recyclerResource();
if (handler != null) {
handler = null;
}
}
}
3.使用代码:
布局文件中:
<com.xaf.marqueelib.MarqueeView
android:id="@+id/marqueeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/marquee_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:horizontal_text_height="40dp"
app:horizontal_text_size="20sp"
app:horizontal_text_width="300dp" />
java代码
MarqueeView marquee = findViewById(R.id.marqueeView);
List<String> contentList = new ArrayList<>();
List<Integer> colorList = new ArrayList<>();
colorList.add(R.color.colorAccent);
contentList.add("1.“网上95%的名人名言都是瞎掰,包括这句。”——鲁迅");
//开启滚动
marquee.setDataAndScroll(contentList, colorList);
//点击事件
marquee.getCurrentView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
Log.d("cccc","hhhh");
}
});
4.res->anim目录下的动画
anim_come_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="800"/>
</set>
anim_get_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0"
android:toYDelta="-100%p"
android:duration="800"/>
</set>
5.res->values目录下的自定义属性:
attrs_marquee.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MarqueeView">
<!--横向文字滚动速度-->
<attr name="horizontal_text_speed" format="integer" />
<!-- 文字大小 -->
<attr name="horizontal_text_size" format="dimension" />
<!-- 横向滚动控件的宽 -->
<attr name="horizontal_text_width" format="dimension" />
<!-- 横向滚动控件的高 -->
<attr name="horizontal_text_height" format="dimension" />
<!--竖向滚动时间-->
<attr name="horizontal_text_switch_time" format="integer" />
</declare-styleable>
</resources>
attrs_text_horizontal.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextHorizontalView">
<!-- 文字颜色 -->
<attr name="text_color" format="color|reference" />
<!-- 文字大小 -->
<attr name="text_size" format="integer" />
<!--每个字的滚动时间-->
<attr name="text_scroll_speed_time" format="boolean" />
</declare-styleable>
</resources>
四.demo:
需要demo的可以去github上下载,顺手点个star。