Android TextView跑马灯实现原理及方法实例
private static final class Marquee {
// 修改此字段设置重跑时间间隔 - 对应不足点2
private static final int MARQUEE_DELAY = 1200;
// 修改此字段设置跑动速度 - 对应不足点1
private static final int MARQUEE_DP_PER_SECOND = 30;
private static final byte MARQUEE_STOPPED = 0x0;
private static final byte MARQUEE_STARTING = 0x1;
private static final byte MARQUEE_RUNNING = 0x2;
private static final String METHOD_GET_FRAME_TIME = "getFrameTime";
private final WeakReference
private final Choreographer mChoreographer;
private byte mStatus = MARQUEE_STOPPED;
private final float mPixelsPerSecond;
private float mMaxScroll;
private float mMaxFadeScroll;
private float mGhostStart;
private float mGhostOffset;
private float mFadeStop;
private int mRepeatLimit;
private float mScroll;
private long mLastAnimationMs;
Marquee(MarqueeTextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
mView = new WeakReference<>(v);
mChoreographer = Choreographer.getInstance();
}
private final Choreographer.FrameCallback mTickCallback = frameTimeNanos -> tick();
private final Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mStatus = MARQUEE_RUNNING;
mLastAnimationMs = getFrameTime();
tick();
}
};
/**
* `getFrameTime`是隐藏api,此处使用反射调用,高系统版本可能失效,可使用某些方案绕过此限制
*/
@SuppressLint("PrivateApi")
private long getFrameTime() {
try {
Class<? extends Choreographer> clz = mChoreographer.getClass();
Method getFrameTime = clz.getDeclaredMethod(METHOD_GET_FRAME_TIME);
getFrameTime.setAccessible(true);
return (long) getFrameTime.invoke(mChoreographer);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
private final Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (mStatus == MARQUEE_RUNNING) {
if (mRepeatLimit >= 0) {
mRepeatLimit--;
}
start(mRepeatLimit);
}
}
};
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
mChoreographer.removeFrameCallback(mTickCallback);
final MarqueeTextView textView = mView.get();
if (textView != null && (textView.isFocused() || textView.isSelected() || textView.isMarquee())) {
long currentMs = getFrameTime();
long deltaMs = currentMs - mLastAnimationMs;
mLastAnimationMs = currentMs;
float deltaPx = deltaMs / 1000F * mPixelsPerSecond;
mScroll += deltaPx;
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
} else {
mChoreographer.postFrameCallback(mTickCallback);
}
textView.invalidate();
}
}
void stop() {
mStatus = MARQUEE_STOPPED;
mChoreographer.removeFrameCallback(mStartCallback);
mChoreographer.removeFrameCallback(mRestartCallback);
mChoreographer.removeFrameCallback(mTickCallback);
resetScroll();
}
private void resetScroll() {
mScroll = 0.0F;
final MarqueeTextView textView = mView.get();
if (textView != null) textView.invalidate();
}
void start(int repeatLimit) {
if (repeatLimit == 0) {
stop();
return;
}
mRepeatLimit = repeatLimit;
final MarqueeTextView textView = mView.get();
if (textView != null && textView.getLayout() != null) {
mStatus = MARQUEE_STARTING;
mScroll = 0.0F;
// 分别计算左右和上下跑动所需的数据
if (textView.getOrientation() == HORIZONTAL) {
int viewWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
textView.getCompoundPaddingRight();
float lineWidth = textView.getLayout().getLineWidth(0);
float gap = viewWidth / 3.0F;
mGhostStart = lineWidth - viewWidth + gap;
mMaxScroll = mGhostStart + viewWidth;
mGhostOffset = lineWidth + gap;
mFadeStop = lineWidth + viewWidth / 6.0F;
mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
} else {
int viewHeight = textView.getHeight() - textView.getCompoundPaddingTop() -
textView.getCompoundPaddingBottom();
float textHeight = textView.getLayout().getHeight();
float gap = viewHeight / 3.0F;
mGhostStart = textHeight - viewHeight + gap;
mMaxScroll = mGhostStart + viewHeight;
mGhostOffset = textHeight + gap;
mFadeStop = textHeight + viewHeight / 6.0F;
mMaxFadeScroll = mGhostStart + textHeight + textHeight;
}
textView.invalidate();
mChoreographer.postFrameCallback(mStartCallback);
}
}
float getGhostOffset() {
return mGhostOffset;
}
float getScroll() {
return mScroll;
}
float getMaxFadeScroll() {
return mMaxFadeScroll;
}
boolean shouldDrawLeftFade() {
return mScroll <= mFadeStop;
}
boolean shouldDrawTopFade() {
return mScroll <= mFadeStop;
}
boolean shouldDrawGhost() {
return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
}
boolean isRunning() {
return mStatus == MARQUEE_RUNNING;
}
boolean isStopped() {
return mStatus == MARQUEE_STOPPED;
}
}
- .NET Core系列之MemoryCache 初识
- 007手机一键Root(安机网一键Root) v3.0 官方最新版 一键ROOT您的Android手机
- 12306密码被盗了怎么办?12306密码外泄解决方法
- 12个字的qq网名
- 150M迷你型无线路由器怎么设置?
- 192.168.1.1打不开怎么办?路由器192.168.1.1打不开的原因以及解决办法
- 2011年电子报合订本 电子报 编辑部 中文 PDF版 [84M]
- 2015年1月15日小米新旗舰发布会现场图文直播
- 2016.3.1vivo Xplay5新品发布会现场视频直播 优酷直播
- 2016华为P9发布会视频直播地址 4月15日华为P9国行发布会直播