[Android] TextView 實現跑馬燈效果(文字自動捲動)

雷哥的開發碎嘴頻道
6 min readApr 19, 2019

--

安安泥好,這是雷哥的Android開發筆記。
想要在元件上做到文字跑馬燈的效果,如下圖所示:

文字跑馬燈(Marquee)

該如何做到呢?其實TextView元件內建就有這個效果了,叫做 marquee

常見解法1: XML屬性設置

第一種常見的作法是在xml直接進行屬性設置。
限定設計於設計稿中是最大的優點。

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textSize="30dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"

android:text="這是一串很長很長,很長很長,很長很長的文字" />

網路上滿多人都推薦使用這個方法,例如這篇Stack Overflow

但是這個解法有個必要條件:他必須要設定焦點 (focus)

焦點是Android系統的概念之一,視為使用者聚焦的地方。通常焦點是使用者的當前點擊對象,而且無法有多個焦點。因此,只要當焦點改變——舉例來說,如果你有一個文字輸入框,甚或是只要有兩個以上TextView需要跑馬燈——這個方法就沒有用。

那麼,該怎麼辦呢?

常用解法2: 客製化TextView獲取焦點

網路上另一種普遍建議是自訂TextView,修改認定焦點的方法——讓所有跑馬燈TextView都被認定是「永遠獲取焦點」的。以下以Java為例:

public class MarqueeTextView extends TextView {
public MarqueeTextView(Context con) {
super(con);
}
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean isFocused() {
return true;
}

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused)
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
@Override
public void onWindowFocusChanged(boolean focused) {
if (focused)
super.onWindowFocusChanged(focused);
}

}

然後使用時改用 MarqueeTextView 即可。

然而,這就是強制設定這些元件獲取焦點了。我不知道這樣會不會有bug,所以我開始找其他解決方法。

於是,我找到了一個更安全的解法:使用selected。

更安全的解法: 改用selected設置

根據這篇介紹以及這篇Stack Overflow,只要設定選定狀態 selected 為true,也能觸發Marquee效果。

只要先將前面的 TextView xml稍做修改(不用設定 focus):

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textSize="30dp"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"

android:text="這是一串很長很長,很長很長,很長很長的文字" />

並且在元件初始化的時候加入 setSelected

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView textView = findViewById(R.id.textView);
textView.setSelected(true);
//...
}

這樣就可以完成了!

文字跑馬燈(Marquee)

目前看起來 setSelected 應該是不會有什麼副作用,像是剛才的Stack Overflow提到:

A view can be selected or not. Note that selection is not the same as focus. Views are typically selected in the context of an AdapterView like ListView or GridView.

除了少數情況會用到selected狀態的時候,可能不適用(像是在ListView裡面,selected狀態有其他的功能)。

以上的code還有純java的版本:

    TextView textView = findViewById(R.id.textView);
textView.setMarqueeRepeatLimit(-1);
textView.setHorizontallyScrolling(true);
textView.setSingleLine(true);

textView.setSelected(true);
//...

這個方法雖然相對「安全」(以Android的架構來說),但由於要動到腳本,架構上相對不乾淨(設計師也要來寫code)。你可以斟酌哪種解法最適合自己的專案(最適合分工以及未來的維護)。

--

--

雷哥的開發碎嘴頻道
雷哥的開發碎嘴頻道

Written by 雷哥的開發碎嘴頻道

站在人生十字路口的萌新開發者。喜歡遊戲開發與安卓App開發。擅長嘴炮與胡思亂想。

No responses yet