Android用TextView實(shí)現(xiàn)跑馬燈效果代碼
【前言】
在Textview設(shè)置的寬度有限,而需要顯示的文字又比較多的情況下,往往需要給Textview設(shè)置跑馬燈效果才能讓用戶(hù)完整地看到所有設(shè)置的文字,所以給TextView設(shè)置跑馬燈效果的需求是很常見(jiàn)的
一、新手設(shè)置跑馬燈效果
1、先在xml中給Textview設(shè)置好對(duì)應(yīng)的屬性
<TextView
android:id="@+id/tv"
android:layout_width="200dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/show_float"
android:singleLine="true"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="-1"
android:layout_marginTop="20dp"
android:padding="10dp"
android:text="歡迎來(lái)到跑馬燈新手村,這是新手示例~"
android:textColor="@color/white"
android:background="@drawable/com_live_rounded_rectangle"/>
2、然后在代碼中設(shè)置請(qǐng)求獲取焦點(diǎn)即可
TextView tv = findViewById(R.id.tv);
tv.requestFocus();
這樣設(shè)置之后,跑馬燈的效果就出來(lái)了

【關(guān)鍵點(diǎn)講解】
1、android:layout_width 是限制為固定寬度,同時(shí)文本的長(zhǎng)度大于所設(shè)置的寬度,要是設(shè)置android:layout_width 為wrap_content, 那么Textview的寬度會(huì)隨著文本長(zhǎng)度變長(zhǎng)而拉寬,這樣就不能出現(xiàn)跑馬燈效果
2、android:singleLine="true"設(shè)置Textview只能一行顯示,要是不設(shè)置為true,默認(rèn)會(huì)自動(dòng)換行,顯示為多行,這樣的話(huà),也不能出現(xiàn)跑馬燈效果
3、android:ellipsize="marquee"設(shè)置要是文本長(zhǎng)度超出Textview的寬度時(shí)候,文本應(yīng)該以跑馬燈效果顯示,這個(gè)是設(shè)置跑馬燈效果最關(guān)鍵的設(shè)置,android:ellipsize還可以取值start、end、middle、none,分別是開(kāi)頭顯示省略號(hào)、結(jié)尾顯示省略號(hào)、中間顯示省略號(hào)、直接截?cái)?/code>
4、android:focusable="true"設(shè)置Textview可以獲取焦點(diǎn),跑馬燈效果需要獲取到焦點(diǎn)時(shí)候才生效,Textview默認(rèn)是不獲取焦點(diǎn)的
5、android:focusableInTouchMode="true"設(shè)置在觸摸模式下可以獲取焦點(diǎn),目前智能機(jī)基本都是自動(dòng)進(jìn)入觸摸模式,其實(shí)目前只要設(shè)置android:focusableInTouchMode="true",默認(rèn)android:focusable也會(huì)變?yōu)閠rue了
6、android:marqueeRepeatLimit="-1"設(shè)置跑馬燈循環(huán)的次數(shù),-1表示無(wú)限循環(huán),不設(shè)置的話(huà),默認(rèn)是循環(huán)3次
7、 tv.requestFocus();設(shè)置獲取焦點(diǎn), 只有當(dāng)該view的focusable屬性為true時(shí)候才生效
【總結(jié)】
1、一定要設(shè)置android:focusableInTouchMode="true",若是只設(shè)置了android:focusable="true"而android:focusableInTouchMode沒(méi)設(shè)置,那么跑馬燈效果是不生效的,因?yàn)檫M(jìn)入觸摸模式之后,isFocusable()返回false,下面看看Texivew startMarquee()源碼就知道需要滿(mǎn)足什么條件才會(huì)開(kāi)始跑馬燈特效:
private void startMarquee() {
// Do not ellipsize EditText
if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
}
// 1、跑馬燈控制類(lèi)沒(méi)有創(chuàng)建或者跑馬燈效果已經(jīng)停止
if ((mMarquee == null || mMarquee.isStopped()) &&
// 2、當(dāng)前Textview是獲取到焦點(diǎn)或者被選中狀態(tài)
(isFocused() || isSelected())
// 3、文本的行數(shù)只有一行
&& getLineCount() == 1
// 4、文本長(zhǎng)度大于Textview的寬度
&& canMarquee()) {
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
final Layout tmp = mLayout;
mLayout = mSavedMarqueeModeLayout;
mSavedMarqueeModeLayout = tmp;
setHorizontalFadingEdgeEnabled(true);
requestLayout();
invalidate();
}
if (mMarquee == null) mMarquee = new Marquee(this);
mMarquee.start(mMarqueeRepeatLimit);
}
}
private boolean canMarquee() {
int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
return width > 0 && (mLayout.getLineWidth(0) > width
|| (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
&& mSavedMarqueeModeLayout.getLineWidth(0) > width));
}
二、高端玩家設(shè)置跑馬燈效果
從上面總結(jié)的TextView跑馬燈源碼可以看到,只要isFocusable()或者isSelected()方法返回true,那么就沒(méi)必要管是否觸摸模式,是否可以獲取焦點(diǎn)之類(lèi)的問(wèn)題了,所以我們可以自定義一個(gè)類(lèi)繼承于TextView,然后重寫(xiě)isFocusable()直接返回true即可:
public class MarqueeTextView extends TextView {
public MarqueeTextView(Context context) {
super(context);
initView(context);
}
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
this.setEllipsize(TextUtils.TruncateAt.MARQUEE);
this.setSingleLine(true);
this.setMarqueeRepeatLimit(-1);
}
//最關(guān)鍵的部分
public boolean isFocused() {
return true;
}
}
1、直接在Xml中使用自定義的MarqueeTextView,那么跑馬燈效果就出來(lái)了,無(wú)需任何額外配置
<com.example.MarqueeTextView
android:id="@+id/tv"
android:layout_width="200dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/show_float"
android:layout_marginTop="20dp"
android:padding="10dp"
android:text="歡迎來(lái)到跑馬燈高端玩家局,這是高端玩法示例~"
android:textColor="@color/white"
android:background="@drawable/com_live_rounded_rectangle"/>
來(lái)看看效果:

三、延伸閱讀
假如有這樣一個(gè)需求:因?yàn)轱@示文本的空間有限,所以只能用跑馬燈的效果來(lái)給用戶(hù)展示文本,但是在用戶(hù)完整地看完一遍文本之后,需要隱藏掉Textview,那么問(wèn)題來(lái)了,我們?cè)趺粗琅荞R燈效果什么時(shí)候跑完一遍呢?先來(lái)看看Textview跑馬燈部分Marquee類(lèi)的部分源碼:
void start(int repeatLimit) {
//重復(fù)次數(shù)設(shè)置0,那就直接停止跑馬燈
if (repeatLimit == 0) {
stop();
return;
}
//...省略掉大部分不相關(guān)的代碼
mChoreographer.postFrameCallback(mStartCallback);
}
}
private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mStatus = MARQUEE_RUNNING;
mLastAnimationMs = mChoreographer.getFrameTime();
tick();
}
};
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
if (textView != null && (textView.isFocused() || textView.isSelected())) {
long currentMs = mChoreographer.getFrameTime();
long deltaMs = currentMs - mLastAnimationMs;
mLastAnimationMs = currentMs;
float deltaPx = deltaMs * mPixelsPerMs;
mScroll += deltaPx;
//要是跑馬燈滾動(dòng)的距離大于最大距離,那么回到給mRestartCallback
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
} else {
mChoreographer.postFrameCallback(mTickCallback);
}
textView.invalidate();
}
}
private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (mStatus == MARQUEE_RUNNING) {
if (mRepeatLimit >= 0) {
mRepeatLimit--;
}
start(mRepeatLimit);
}
}
}
從上面對(duì)Marquee源碼分析可知,跑馬燈跑完一輪之后會(huì)調(diào)用到Marquee類(lèi) mRestartCallback對(duì)象的doFrame方法,那么我們來(lái)一招“偷龍轉(zhuǎn)鳳”,通過(guò)反射把mRestartCallback對(duì)象替換成我們自己實(shí)例化的對(duì)象,那么在跑馬燈跑完一輪之后就會(huì)回調(diào)到我們替換的對(duì)象中,這樣就實(shí)現(xiàn)了對(duì)跑馬燈效果跑完一輪的監(jiān)聽(tīng),實(shí)現(xiàn)源碼如下:
public class MarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {
private Choreographer.FrameCallback mRealRestartCallbackObj;
private Choreographer.FrameCallback mFakeRestartCallback;
private OnShowTextListener mOnShowTextListener;
public MarqueeTextView(Context context, OnShowTextListener onShowTextListener) {
super(context);
initView(context);
this.mOnShowTextListener = onShowTextListener;
}
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
//繞過(guò)隱藏api的限制
Reflection.unseal(context.getApplicationContext());
//設(shè)置跑馬燈生效條件
this.setEllipsize(TextUtils.TruncateAt.MARQUEE);
this.setSingleLine(true);
this.setFocusable(true);
//反射設(shè)置跑馬燈監(jiān)聽(tīng)
try {
//從TextView類(lèi)中找到定義的字段mMarquee
Field marqueeField = ReflectUtil.getDeclaredField(TextView.class, "mMarquee");
//獲取Marquee類(lèi)的構(gòu)造方法Marquee(TextView v)
Constructor declaredConstructor = ReflectUtil.getDeclaredConstructor(Class.forName("android.widget.TextView$Marquee"), TextView.class);
//實(shí)例化一個(gè)Marquee對(duì)象,傳入?yún)?shù)是Textview對(duì)象
Object marqueeObj = declaredConstructor.newInstance(this);
//從Marquee類(lèi)中找到定義的字段mRestartCallback,重新開(kāi)始一輪跑馬燈時(shí)候會(huì)回調(diào)到這個(gè)對(duì)象doFrame()方法
Field restartCallbackField = ReflectUtil.getDeclaredField(Class.forName("android.widget.TextView$Marquee"), "mRestartCallback");
//從Marquee實(shí)例對(duì)象中獲取到真實(shí)的mRestartCallback對(duì)象
mRealRestartCallbackObj = (Choreographer.FrameCallback) restartCallbackField.get(marqueeObj);
//構(gòu)造一個(gè)假的mRestartCallback對(duì)象,用來(lái)監(jiān)聽(tīng)什么時(shí)候跑完一輪跑馬燈效果
mFakeRestartCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//這里還是執(zhí)行真實(shí)的mRestartCallback對(duì)象的代碼邏輯
mRealRestartCallbackObj.doFrame(frameTimeNanos);
Log.i("min77","跑馬燈文本顯示完畢");
//回調(diào)通知跑完一輪
if(MarqueeTextView.this.mOnShowTextListener != null){
MarqueeTextView.this.mOnShowTextListener.onComplete(0);
}
}
};
//把假的mRestartCallback對(duì)象設(shè)置給Marquee對(duì)象,其實(shí)就是代理模式
restartCallbackField.set(marqueeObj, mFakeRestartCallback);
//把自己實(shí)例化的Marquee對(duì)象設(shè)置給Textview
marqueeField.set(this, marqueeObj);
} catch (Exception e) {
e.printStackTrace();
Log.e("min77",e.getMessage());
}
}
//最關(guān)鍵的部分
public boolean isFocused() {
return true;
}
/**
* 是否顯示完整文本
*/
public interface OnShowTextListener{
void onComplete(int delayMillisecond);
}
}
效果如下:

總結(jié)
到此這篇關(guān)于Android TextView實(shí)現(xiàn)跑馬燈效果代碼的文章就介紹到這了,更多相關(guān)Android TextView跑馬燈效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Android中Runtime解決屏幕旋轉(zhuǎn)問(wèn)題(推薦)
這篇文章主要介紹了Runtime解決屏幕旋轉(zhuǎn)問(wèn)題的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Flutter?隊(duì)列任務(wù)的實(shí)現(xiàn)
本文主要介紹了Flutter?隊(duì)列任務(wù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Android中常用的三個(gè)Dialog彈窗總結(jié)解析
自己雖然一直使用過(guò)dialog,但是一直都是復(fù)制、粘貼;不清楚dialog的具體用途,這次趁著有時(shí)間,總結(jié)一下具體用法,感興趣的朋友跟著小編來(lái)看看吧2021-10-10
Android瀑布流照片墻實(shí)現(xiàn) 體驗(yàn)不規(guī)則排列的美感
這篇文章主要為大家詳細(xì)介紹了Android瀑布流照片墻實(shí)現(xiàn),體驗(yàn)不規(guī)則排列的美感,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android仿人人客戶(hù)端滑動(dòng)菜單的側(cè)滑菜單效果
這篇文章主要介紹了Android仿人人客戶(hù)端滑動(dòng)菜單的側(cè)滑特效實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,分享給大家供大家參考2018-05-05
解決Android Studio 代碼無(wú)提示無(wú)顏色區(qū)分問(wèn)題
這篇文章主要介紹了解決Android Studio 代碼無(wú)提示無(wú)顏色區(qū)分問(wèn)題,需要的朋友可以參考下2018-08-08
Android Handler內(nèi)存泄漏原因及解決方案
這篇文章主要介紹了Android Handler內(nèi)存泄漏原因及解決方案,幫助大家更好的理解和利用Android進(jìn)行開(kāi)發(fā),感興趣的朋友可以了解下2021-02-02
Android基于Flutter編寫(xiě)文件下載管理器
文件下載在很多類(lèi)型的應(yīng)用中會(huì)涉及,例如音樂(lè)、文檔、包括圖片(只是圖片可以使用一些組件完成無(wú)感知的下載)。本篇介紹使用Flutter中的Dio下載方法完成文件的下載,需要的可以參考一下2022-03-03
android studio實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器(無(wú)bug)
這篇文章主要為大家詳細(xì)介紹了android studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08

