安安泥好,這是雷哥的Android開發筆記。
想要在元件上做到文字跑馬燈的效果,如下圖所示:
該如何做到呢?其實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);
//...
}
這樣就可以完成了!
目前看起來 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)。你可以斟酌哪種解法最適合自己的專案(最適合分工以及未來的維護)。