前言
繼承TextView,並仿照原始碼修改而來,主要是取消了焦點和選中了判斷,也不依賴文字的寬度。
宣告
歡迎轉載,但請保留文章原始出處:)
部落格園:http://www.cnblogs.com
農民伯伯: http://over140.cnblogs.com
正文
import java.lang.ref.WeakReference;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.TextView;
public class MarqueeTextView extends TextView {
private Marquee mMarquee;
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MarqueeTextView(Context context) {
super(context);
}
public void startMarquee() {
startMarquee(-1);
}
public void startMarquee(int repeatLimit) {
if (mMarquee == null)
mMarquee = new Marquee(this);
mMarquee.start(repeatLimit);
}
public void stopMarquee() {
if (mMarquee != null && !mMarquee.isStopped()) {
mMarquee.stop();
}
}
public void toggleMarquee() {
if (mMarquee == null || mMarquee.isStopped())
startMarquee();
else
stopMarquee();
}
@Override
protected void onDraw(Canvas canvas) {
if (mMarquee != null && mMarquee.isRunning()) {
final float dx = -mMarquee.getScroll();
canvas.translate(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -dx
: +dx, 0.0f);
}
super.onDraw(canvas);
}
@SuppressWarnings("unused")
private static final class Marquee extends Handler {
// TODO: Add an option to configure this
private static final float MARQUEE_DELTA_MAX = 0.07f;
private static final int MARQUEE_DELAY = 0;// 1200;
private static final int MARQUEE_RESTART_DELAY = 1200;
private static final int MARQUEE_RESOLUTION = 1000 / 30;
private static final int MARQUEE_PIXELS_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 int MESSAGE_START = 0x1;
private static final int MESSAGE_TICK = 0x2;
private static final int MESSAGE_RESTART = 0x3;
private final WeakReference<TextView> mView;
private byte mStatus = MARQUEE_STOPPED;
private final float mScrollUnit;
private float mMaxScroll;
private float mMaxFadeScroll;
private float mGhostStart;
private float mGhostOffset;
private float mFadeStop;
private int mRepeatLimit;
private float mScroll;
Marquee(TextView v) {
final float density = v.getContext().getResources()
.getDisplayMetrics().density;
mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density)
/ MARQUEE_RESOLUTION;
mView = new WeakReference<TextView>(v);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_START:
mStatus = MARQUEE_RUNNING;
tick();
break;
case MESSAGE_TICK:
tick();
break;
case MESSAGE_RESTART:
if (mStatus == MARQUEE_RUNNING) {
if (mRepeatLimit >= 0) {
mRepeatLimit--;
}
start(mRepeatLimit);
}
break;
}
}
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
removeMessages(MESSAGE_TICK);
final TextView textView = mView.get();
// && (textView.isFocused() || textView.isSelected())
if (textView != null) {
mScroll += mScrollUnit;
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
sendEmptyMessageDelayed(MESSAGE_RESTART,
MARQUEE_RESTART_DELAY);
} else {
sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
}
textView.invalidate();
}
}
void stop() {
mStatus = MARQUEE_STOPPED;
removeMessages(MESSAGE_START);
removeMessages(MESSAGE_RESTART);
removeMessages(MESSAGE_TICK);
resetScroll();
}
private void resetScroll() {
mScroll = 0.0f;
final TextView textView = mView.get();
if (textView != null) {
textView.invalidate();
}
}
void start(int repeatLimit) {
if (repeatLimit == 0) {
stop();
return;
}
mRepeatLimit = repeatLimit;
final TextView textView = mView.get();
if (textView != null && textView.getLayout() != null) {
mStatus = MARQUEE_STARTING;
mScroll = 0.0f;
final int textWidth = textView.getWidth()
- textView.getCompoundPaddingLeft()
- textView.getCompoundPaddingRight();
final float lineWidth = textView.getLayout().getLineWidth(0);
final float gap = textWidth / 3.0f;
mGhostStart = lineWidth - textWidth + gap;
mMaxScroll = mGhostStart + textWidth;
mGhostOffset = lineWidth + gap;
mFadeStop = lineWidth + textWidth / 6.0f;
mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
textView.invalidate();
sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
}
}
float getGhostOffset() {
return mGhostOffset;
}
float getScroll() {
return mScroll;
}
float getMaxFadeScroll() {
return mMaxFadeScroll;
}
boolean shouldDrawLeftFade() {
return mScroll <= mFadeStop;
}
boolean shouldDrawGhost() {
return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
}
boolean isRunning() {
return mStatus == MARQUEE_RUNNING;
}
boolean isStopped() {
return mStatus == MARQUEE_STOPPED;
}
}
}
import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.TextView;
public class MarqueeTextView extends TextView {
private Marquee mMarquee;
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MarqueeTextView(Context context) {
super(context);
}
public void startMarquee() {
startMarquee(-1);
}
public void startMarquee(int repeatLimit) {
if (mMarquee == null)
mMarquee = new Marquee(this);
mMarquee.start(repeatLimit);
}
public void stopMarquee() {
if (mMarquee != null && !mMarquee.isStopped()) {
mMarquee.stop();
}
}
public void toggleMarquee() {
if (mMarquee == null || mMarquee.isStopped())
startMarquee();
else
stopMarquee();
}
@Override
protected void onDraw(Canvas canvas) {
if (mMarquee != null && mMarquee.isRunning()) {
final float dx = -mMarquee.getScroll();
canvas.translate(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -dx
: +dx, 0.0f);
}
super.onDraw(canvas);
}
@SuppressWarnings("unused")
private static final class Marquee extends Handler {
// TODO: Add an option to configure this
private static final float MARQUEE_DELTA_MAX = 0.07f;
private static final int MARQUEE_DELAY = 0;// 1200;
private static final int MARQUEE_RESTART_DELAY = 1200;
private static final int MARQUEE_RESOLUTION = 1000 / 30;
private static final int MARQUEE_PIXELS_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 int MESSAGE_START = 0x1;
private static final int MESSAGE_TICK = 0x2;
private static final int MESSAGE_RESTART = 0x3;
private final WeakReference<TextView> mView;
private byte mStatus = MARQUEE_STOPPED;
private final float mScrollUnit;
private float mMaxScroll;
private float mMaxFadeScroll;
private float mGhostStart;
private float mGhostOffset;
private float mFadeStop;
private int mRepeatLimit;
private float mScroll;
Marquee(TextView v) {
final float density = v.getContext().getResources()
.getDisplayMetrics().density;
mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density)
/ MARQUEE_RESOLUTION;
mView = new WeakReference<TextView>(v);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_START:
mStatus = MARQUEE_RUNNING;
tick();
break;
case MESSAGE_TICK:
tick();
break;
case MESSAGE_RESTART:
if (mStatus == MARQUEE_RUNNING) {
if (mRepeatLimit >= 0) {
mRepeatLimit--;
}
start(mRepeatLimit);
}
break;
}
}
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
removeMessages(MESSAGE_TICK);
final TextView textView = mView.get();
// && (textView.isFocused() || textView.isSelected())
if (textView != null) {
mScroll += mScrollUnit;
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
sendEmptyMessageDelayed(MESSAGE_RESTART,
MARQUEE_RESTART_DELAY);
} else {
sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
}
textView.invalidate();
}
}
void stop() {
mStatus = MARQUEE_STOPPED;
removeMessages(MESSAGE_START);
removeMessages(MESSAGE_RESTART);
removeMessages(MESSAGE_TICK);
resetScroll();
}
private void resetScroll() {
mScroll = 0.0f;
final TextView textView = mView.get();
if (textView != null) {
textView.invalidate();
}
}
void start(int repeatLimit) {
if (repeatLimit == 0) {
stop();
return;
}
mRepeatLimit = repeatLimit;
final TextView textView = mView.get();
if (textView != null && textView.getLayout() != null) {
mStatus = MARQUEE_STARTING;
mScroll = 0.0f;
final int textWidth = textView.getWidth()
- textView.getCompoundPaddingLeft()
- textView.getCompoundPaddingRight();
final float lineWidth = textView.getLayout().getLineWidth(0);
final float gap = textWidth / 3.0f;
mGhostStart = lineWidth - textWidth + gap;
mMaxScroll = mGhostStart + textWidth;
mGhostOffset = lineWidth + gap;
mFadeStop = lineWidth + textWidth / 6.0f;
mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
textView.invalidate();
sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
}
}
float getGhostOffset() {
return mGhostOffset;
}
float getScroll() {
return mScroll;
}
float getMaxFadeScroll() {
return mMaxFadeScroll;
}
boolean shouldDrawLeftFade() {
return mScroll <= mFadeStop;
}
boolean shouldDrawGhost() {
return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
}
boolean isRunning() {
return mStatus == MARQUEE_RUNNING;
}
boolean isStopped() {
return mStatus == MARQUEE_STOPPED;
}
}
}
程式碼說明:
1、取消了焦點和選中的判斷
2、將延遲1200改為0,立即執行跑馬燈效果。
3、核心程式碼都是直接從TextView拷貝出來。
2014-04-25 更新
強烈建議參考本文的最新版本:【Android】不依賴焦點和選中的TextView跑馬燈【2】
結束
這裡主要是提供一種解決問題的思路,實際使用還需要進行相應的修改。