Android用TextView實(shí)現(xiàn)跑馬燈效果代碼
【前言】
在Textview設(shè)置的寬度有限,而需要顯示的文字又比較多的情況下,往往需要給Textview設(shè)置跑馬燈效果才能讓用戶完整地看到所有設(shè)置的文字,所以給TextView設(shè)置跑馬燈效果的需求是很常見的
一、新手設(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="歡迎來到跑馬燈新手村,這是新手示例~" 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è)置之后,跑馬燈的效果就出來了
【關(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)換行,顯示為多行,這樣的話,也不能出現(xiàn)跑馬燈效果
3、android:ellipsize="marquee"
設(shè)置要是文本長(zhǎng)度超出Textview的寬度時(shí)候,文本應(yīng)該以跑馬燈效果顯示,這個(gè)是設(shè)置跑馬燈效果最關(guān)鍵的設(shè)置,android:ellipsize
還可以取值start
、end
、middle
、none
,分別是開頭顯示省略號(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表示無限循環(huán),不設(shè)置的話,默認(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
沒設(shè)置,那么跑馬燈效果是不生效的,因?yàn)檫M(jìn)入觸摸模式之后,isFocusable()
返回false,下面看看Texivew startMarquee()
源碼就知道需要滿足什么條件才會(huì)開始跑馬燈特效:
private void startMarquee() { // Do not ellipsize EditText if (getKeyListener() != null) return; if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { return; } // 1、跑馬燈控制類沒有創(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,那么就沒必要管是否觸摸模式,是否可以獲取焦點(diǎn)之類的問題了,所以我們可以自定義一個(gè)類繼承于TextView,然后重寫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,那么跑馬燈效果就出來了,無需任何額外配置
<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="歡迎來到跑馬燈高端玩家局,這是高端玩法示例~" android:textColor="@color/white" android:background="@drawable/com_live_rounded_rectangle"/>
來看看效果:
三、延伸閱讀
假如有這樣一個(gè)需求:因?yàn)轱@示文本的空間有限,所以只能用跑馬燈的效果來給用戶展示文本,但是在用戶完整地看完一遍文本之后,需要隱藏掉Textview,那么問題來了,我們?cè)趺粗琅荞R燈效果什么時(shí)候跑完一遍呢?先來看看Textview跑馬燈部分Marquee
類的部分源碼:
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
類 mRestartCallback
對(duì)象的doFrame
方法,那么我們來一招“偷龍轉(zhuǎn)鳳”,通過反射把mRestartCallback
對(duì)象替換成我們自己實(shí)例化的對(duì)象,那么在跑馬燈跑完一輪之后就會(huì)回調(diào)到我們替換的對(duì)象中,這樣就實(shí)現(xiàn)了對(duì)跑馬燈效果跑完一輪的監(jiān)聽,實(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) { //繞過隱藏api的限制 Reflection.unseal(context.getApplicationContext()); //設(shè)置跑馬燈生效條件 this.setEllipsize(TextUtils.TruncateAt.MARQUEE); this.setSingleLine(true); this.setFocusable(true); //反射設(shè)置跑馬燈監(jiān)聽 try { //從TextView類中找到定義的字段mMarquee Field marqueeField = ReflectUtil.getDeclaredField(TextView.class, "mMarquee"); //獲取Marquee類的構(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類中找到定義的字段mRestartCallback,重新開始一輪跑馬燈時(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ì)象,用來監(jiān)聽什么時(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 多線程的實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了Android 多線程的實(shí)現(xiàn)方法總結(jié)的相關(guān)資料,這里提供三種方法,幫助大家掌握這部分內(nèi)容,需要的朋友可以參考下2017-08-08android自定義view之實(shí)現(xiàn)日歷界面實(shí)例
本篇文章主要介紹了android自定義view之實(shí)現(xiàn)日歷界面實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03Android自定義流式布局的實(shí)現(xiàn)示例
這篇文章主要介紹了Android自定義流式布局的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12TabLayout+ViewPager2的簡(jiǎn)單使用詳解
這篇文章主要為大家詳細(xì)介紹了TabLayout+ViewPager2的簡(jiǎn)單使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09android通過配置文件設(shè)置應(yīng)用安裝到SD卡上的方法
在AndroidManifest.xml文件的manifest里面加上一句話,就可以把應(yīng)用安裝到SD卡上2013-11-11