跑马灯在我们日常使用的app中还是很常见的,以前做外卖app的时候商家公告就使用了此效果,但是它是横向滚动的,横向滚动多适用于单条信息;但凡涉及到多条信息的滚动展示,用纵向滚动效果会有更好的用户体验,今天我们通过自定义View来看看如何实现纵向跑马灯效果。
思路
通过上面的gif图可以得出结论,其实它就是同时绘制两条文本信息,然后通过动画不断的改变两条文本信息距离顶部的高度,以此来实现滚动的效果。
具体实现
- 首先定义一些要用到的属性
动画开始延迟时间
动画重复延迟时间
单个动画的执行时间
- 接下来解析属性值
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MarqueeViewStyle);
mTextColor = typedArray.getColor(R.styleable.MarqueeViewStyle_textColor, Color.BLACK);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_textSize, 45);
mPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingLeft, 15);
mPaddingTop = mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingTopBottom, 25);
mPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingTop, mPaddingTop);
mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingBottom, mPaddingBottom);
itemAnimationTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_itemAnimationTime, 1000);
reRepeatDelayTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_reRepeatDelayTime, 1000);
startDelayTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_startDelayTime, 500);
typedArray.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
mPaint.setTextAlign(Paint.Align.LEFT);}
- 重写onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(mTextArray == null || mTextArray.length == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
ViewGroup.LayoutParams lp = getLayoutParams();
setMeasuredDimension(getViewWidth(lp, width), getViewHeight(lp, height));
}
}
数据为空时调用父类的方法,设置数据以后根据不同的布局计算宽高。
- 设置数据
public void setTextArray(String[] textArray) {
if(textArray == null || textArray.length <= 1) return;
mTextArray = textArray;
initTextRect();
setTextCurrentOrNextStatus(0, 1, true);
startAnimation();}
initTextRect()方法是计算出单个文本的高度和数组中最大文本的宽度,文本的高度用来计算绘制文本时的位置,文本的最大宽度在onMeasure()方法的时候会用到。
setTextCurrentOrNextStatus()方法设置当前的position,文本以及下一个position,文本。还有文本距离顶部的初始化高度。
startAnimation() 方法 开始执行动画。
- 动画实现
private void startAnimation() {
va = ValueAnimator.ofFloat(0, 1);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mProgress = (float) animation.getAnimatedValue();
. int moveOffset = (int) (mTextMoveOffset * mProgress);
mCurrentTextMoveMarginTop = mCurrentTextInitMarginTop - moveOffset;
mNextTextMoveMarginTop = mNextTextInitMarginTop - moveOffset;
postInvalidate();
}
});
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
va.pause();
setTextCurrentOrNextStatus(mNextTextPosition, mNextTextPosition + 1, false);
handler.postDelayed(new Runnable() {
@Override
public void run() {
if(!mIsPause) {
va.resume();
}
}
}, reRepeatDelayTime);
}
});
va.setRepeatCount(-1);
va.setDuration(itemAnimationTime);
va.setStartDelay(startDelayTime);
va.start();
}
va.setRepeatCount(-1); 设置动画无限重复
onAnimationUpdate() 方法得到动画执行的进度,计算出text距离顶部的距离,调用postInvalidate()方法刷新界面。
onAnimationRepeat() 方法,通过handler延迟reRepeatDelayTime时间,再重新执行动画。
- 绘制
protected void onDraw(Canvas canvas) {
if(mTextArray == null || mTextArray.length == 0) {
super.onDraw(canvas);
} else {
canvas.drawText(mCurrentText, mPaddingLeft, mCurrentTextMoveMarginTop, mPaint);
canvas.drawText(mNextText, mPaddingLeft, mNextTextMoveMarginTop, mPaint);
}
}
- 结束
至此所有的代码已经分析完毕,其实实现这个效果还有很多种方法,通过继承ViewGroup等等都可以实现,大家有兴趣可以自己尝试。
最后放上源码 https://github.com/chenpengfei88/VerticalMarqueeView
有兴趣的朋友可以看看,欢迎大家Start,Follow。