【Android】實現走馬燈並可設定速度
一、前言
使用TextView實現走馬燈效果非常的簡單,只需要在佈局裡新增一個如下的TextView;
<TextView android:id="@+id/marquee" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" android:focusable="true" android:focusableInTouchMode="true" />
但是,當TextView失去焦點時(例如我們有兩個走馬燈,或者觸發彈窗),走馬燈會暫停(有興趣的可以自己試一下),等到重新獲取到焦點時,才繼續;
二、實現自定義TextView
為了解決上述問題,我們可以自定義一個MarqueeView,繼承自TextView,並讓他一直有焦♂點;
package com.mory.MarqueeView; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Rect; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatTextView; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import java.lang.reflect.Field; public class MarqueeView extends AppCompatTextView { private static final String TAG = "MarqueeView"; public MarqueeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (focused) { super.onFocusChanged(true, direction, previouslyFocusedRect); } } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (hasWindowFocus) { super.onWindowFocusChanged(true); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } private void init() { setSingleLine(); setEllipsize(TextUtils.TruncateAt.MARQUEE); setMarqueeRepeatLimit(-1); setFocusable(true); setFocusableInTouchMode(true); } }
上述程式碼中做的操作很簡單,只是把佈局中配置的屬性放到程式碼中完成;
三、使用自定義View
在我們的佈局中,使用MarqueeView;
<com.mory.MarqueeView android:id="@+id/marquee" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" />
當目前為止,已經可以正常使用的走馬燈了;
但是,有的小朋友問了(無中生友):咱們怎麼設定它的滾動速度呢
四、 利用反射自定義速度
我在網路上看到的實現方式都有些複雜,有著大量的程式碼和執行緒互動(例如io執行緒輪詢通過handler通知ui執行緒更新),因此決定嘗試使用反射方式,通過修改系統定義的速度來實現;
/** * 利用反射 設定跑馬燈的速度 * 在onLayout中呼叫即可 * * @param newSpeed 新的速度 */ @SuppressLint("PrivateApi") private void setMarqueeSpeed(float newSpeed) { try { // 獲取走馬燈配置物件 Class tvClass = Class.forName("android.widget.TextView"); Field marqueeField = tvClass.getDeclaredField("mMarquee"); marqueeField.setAccessible(true); Object marquee = marqueeField.get(this); if (marquee == null) { return; } // 設定新的速度 Class<?> marqueeClass = marquee.getClass(); /* // 速度變數的名稱可能與此示例的不相同 可自行列印檢視 for (Field field : marqueeClass.getDeclaredFields()) { Log.i(TAG, field.getName()); } */ // SDK中的是mPixelsPerMs,但我的開發機是下面的名稱 Field speedField = marqueeClass.getDeclaredField("mPixelsPerSecond"); speedField.setAccessible(true); float orgSpeed = (float) speedField.get(marquee); // 這裡設定了相對於原來的20倍 speedField.set(marquee, newSpeed); Log.i(TAG, "setMarqueeSpeed: " + orgSpeed); Log.i(TAG, "setMarqueeSpeed: " + newSpeed); } catch (ClassNotFoundException | NoSuchFieldException e) { Log.e(TAG, "setMarqueeSpeed: 設定跑馬燈速度失敗", e); } catch (IllegalAccessException e) { e.printStackTrace(); } }
這裡踩了一個坑,我的Compile SDK是28的,SDK原始碼中變數名叫mPixelsPerMs
,我的開發機並不是這個(可能是版本不同,開發機是22);
如果你和我一樣也是固定機型,不考慮適配的話,可以解開裡面的多行註釋,然後自己找找是哪個名稱;
如果需要適配,可以試試,看看不同版本的SDK下變數名分別叫什麼;