dependencies {
com.xiaosu:VerticalRollingTextView:1.2.0
}
List textArray = ...;
verticalRollingView.setDataSetAdapter(new DataSetAdapter(textArray) {
@Override
protected String text(String s) {
return s;
}
});
// 开始滚动
verticalRollingView.run();
// 停止滚动
verticalRollingView.stop();
// 设置点击监听
verticalRollingView.setOnItemClickListener(new VerticalRollingTextView.OnItemClickListener() {
@Override
public void onItemClick(VerticalRollingTextView view, int index) {
// your code
}
});
public VerticalRollingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔,绘制文字使用
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLACK);
mPaint.setTypeface(Typeface.DEFAULT);
// 解析自定义属性
parseAttrs(context, attrs);
// 计算出top到ascent的距离,后面在onDraw()方法中调用
Paint.FontMetricsInt metricsInt = mPaint.getFontMetricsInt();
mTextTopToAscentOffset = metricsInt.ascent - metricsInt.top;
mAnimation.setDuration(mDuration);
}
关于FontMetricsInt类中的top和ascent属性,可参考下图。
// 开启动画
public void run() {
if (isRunning) {
return;
}
isRunning = true;
post(mRollingTask);
}
private InternalAnimation mAnimation = new InternalAnimation();
// 循环的任务
Runnable mRollingTask = new Runnable() {
@Override
public void run() {
mAnimationEnded = false;
startAnimation(mAnimation);
postDelayed(this, mAnimInterval);
}
};
InternalAnimation是一个自定义动画类,继承自Animation类,实现了applyTransformation()方法。在applyTransformation()中,使用参数interpolatedTime在动画执行中由0.0递增到1.0的特性,通过计算不断的减小mCurrentOffsetY的值,并调用postInvalidate()方法触发View类的onDraw()方法调用。当mCurrentOffsetY减小到endValue时,动画结束,调用animationEnd()方法。
class InternalAnimation extends Animation {
float startValue;
float endValue;
// 动画执行过程中,interpolatedTime由0.0递增到1.0
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mAnimationEnded)
return;
// 不断的修改mCurrentOffsetY的值,mCurrentOffsetY取值范围:startValue~endValue
mCurrentOffsetY = evaluate(interpolatedTime, startValue, endValue);
// mCurrentOffsetY的值由startValue减小到endValue时,动画结束
if (mCurrentOffsetY == endValue) {
animationEnd();
}
// 调用onDraw()方法绘制
postInvalidate();
}
// 为成员变量startValue和endValue赋值
public void updateValue(float startValue, float endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
}
// float估值器
float evaluate(float fraction, float startValue, float endValue) {
return startValue + fraction * (endValue - startValue);
}
// 绘制文本
@Override
protected void onDraw(Canvas canvas) {
if (mDataSetAdapter == null || mDataSetAdapter.isEmpty()) {
return;
}
// 获取当前需要显示的文字
String text1 = mDataSetAdapter.getText(mCurrentIndex);
// 获取下一个需要显示的文字
String text2 = mDataSetAdapter.getText(mNextIndex);
// 只需要进行一次测量
if (mOrgOffsetY == -1) {
mPaint.getTextBounds(text1, 0, text1.length(), bounds);
// mOffset:文字底部到父视图顶部的距离
mOffset = (getHeight() + bounds.height()) * 0.5f;
// mOrgOffsetY:文字在垂直方向居中时,向上滚动的偏移量
mOrgOffsetY = mCurrentOffsetY = mOffset - mTextTopToAscentOffset;
// 初始化InternalAnimation中的成员变量
mAnimation.updateValue(mOrgOffsetY, -2 * mTextTopToAscentOffset);
}
// 不断的绘制文本。x:始终为0不变;y:不断的减小
canvas.drawText(text1, 0, mCurrentOffsetY, mPaint);
canvas.drawText(text2, 0, mCurrentOffsetY + mOffset + mTextTopToAscentOffset, mPaint);
}
// 动画执行完成
public void animationEnd() {
// 索引加一,取下一条数据
mCurrentIndex++;
// 因为文字是一直循环滚动下去的,这里使用取余数确保不会越界
mCurrentIndex = mCurrentIndex < mDataSetAdapter.getItemCount() ? mCurrentIndex
: mCurrentIndex % mDataSetAdapter.getItemCount();
//计算下一个待显示文字角标
confirmNextIndex();
// 该轮动画完成,接下来要开始新一轮动画,重置mCurrentOffsetY变量
mCurrentOffsetY = mOrgOffsetY;
mAnimationEnded = true;
}
// 计算当前索引的下一条数据
private void confirmNextIndex() {
// mNextIndex是在当前索引mCurrentIndex之后
mNextIndex = mCurrentIndex + 1;
// 如果mCurrentIndex已经是最后一条,mNextIndex再从0开始
mNextIndex = mNextIndex < mDataSetAdapter.getItemCount() ? mNextIndex : 0;
}