在工作中難免遇到自定義
View
的相關(guān)需求腋粥,本身這方面比較薄弱蚊荣,因此做個(gè)記錄,也是自己學(xué)習(xí)和成長(zhǎng)的積累秫筏。自定義View實(shí)戰(zhàn)
前言
最近公司接到小需求--「可以滾動(dòng)的提示」诱鞠,其實(shí)就是跑馬燈。這讓我想到了大學(xué)時(shí)專業(yè)物聯(lián)網(wǎng)这敬,當(dāng)時(shí)學(xué)的單片機(jī)入門教程就是跑馬燈航夺,很是親切。其實(shí)就是燈(或文字)按照某個(gè)方向循環(huán)滾動(dòng)崔涂。
Android 原生的跑馬燈
其實(shí)阳掐,Android
中的TextView
自帶跑馬燈效果,只需要通過簡(jiǎn)單的配置冷蚂,就可以完成滾動(dòng)的效果缭保。
在XML
中進(jìn)行配置
<TextView
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:text="我是跑馬燈,我是跑馬燈帝雇,我是跑馬燈涮俄,我是跑馬燈,我是跑馬燈尸闸,我是跑馬"
android:textSize="28sp" />
可以看到需要很多的屬性配置彻亲,了解一下每個(gè)屬性的含義:
-
android:ellipsize="marquee"
設(shè)置為跑馬燈效果 -
android:focusable="true"
獲取焦點(diǎn) -
android:focusableInTouchMode="true"
touch 時(shí)獲取焦點(diǎn) -
android:marqueeRepeatLimit="marquee_forever"
設(shè)置重復(fù)次數(shù) -
android:scrollHorizontally="true"
設(shè)置為水平滾動(dòng) -
android:singleLine="true"
單行顯示
按照上面的配置,正常情況下是可以運(yùn)轉(zhuǎn)的吮廉,但是用到項(xiàng)目中的時(shí)候苞尝,會(huì)發(fā)現(xiàn)很多bug
和不足之處。
比如宦芦,偶爾突然不滾動(dòng)了宙址,具體的原因是沒有獲取到焦點(diǎn)。我覺得這是原生跑馬燈最坑的一點(diǎn)调卑,必須獲取到焦點(diǎn)才能正常運(yùn)行抡砂。
當(dāng)然解決方式也有大咱,第一種,通過主動(dòng)獲取焦點(diǎn)的方式注益,即調(diào)用view.setFocusable(true)
碴巾。還有一種就是重寫TextView
的isFocused()
方法,強(qiáng)制讓他獲取焦點(diǎn)丑搔。
@Override
public boolean isFocused() {
return true;
}
就算這樣厦瓢,在遇到復(fù)雜的界面還是會(huì)遇到問題,要么焦點(diǎn)會(huì)被斷斷續(xù)續(xù)的被搶奪啤月,導(dǎo)致卡頓煮仇,要么不符合UI
提出的滾動(dòng)速度要求。
自定義跑馬燈
鑒于這個(gè)背景谎仲,通過Scroller
完成自定義的跑馬燈浙垫,代碼已上傳至GitHub
上:MarqueeTextView
先看一下整體的效果:
如果想直接使用,在根build.gradle
配置:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
在app
下的build.gradle
添加依賴
dependencies {
compile 'com.github.xiaweizi:MarqueeTextView:1.0'
}
最后在XML
直接使用即可:
<com.xiaweizi.marquee.MarqueeTextView
android:id="@+id/marquee1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/string1"
android:textColor="#ff0000"
android:textSize="18sp"
app:scroll_first_delay="0"
app:scroll_interval="2000"
app:scroll_mode="mode_forever" />
具有一下功能:
- 控制滾動(dòng)時(shí)間
- 控制滾動(dòng)延遲
- 控制滾動(dòng)模式
- 生命周期可以自己控制
- 暫停
- 繼續(xù)
- 重新開始
- 停止
實(shí)現(xiàn)原理
通過Scroller
控制器來控制整個(gè)View
的滾動(dòng)强重,那什么是Scroller
绞呈,做個(gè)簡(jiǎn)單的介紹。
Scroller
內(nèi)部封裝了滾動(dòng)的操作间景,通過構(gòu)造函數(shù)中傳入插值器佃声。可以控制起始位置和整個(gè)滾動(dòng)的時(shí)間倘要,并且通過computeScrollOffset()
得到滾動(dòng)動(dòng)作是否結(jié)束圾亏。
最核心的方法有兩個(gè):
-
startScroll
/** * @param startX 水平方向滾動(dòng)的偏移值,以像素為單位封拧。 * @param startY 垂直方向滾動(dòng)的偏移值志鹃,以像素為單位 * @param dx 水平方向滾動(dòng)的距離 * @param dy 垂直方向滾動(dòng)的距離 * @param duration 滾動(dòng)持續(xù)的時(shí)間,以毫秒為單位 */ public void startScroll (int startX, int startY, int dx, int dy, int duration) { ... }
-
computeScrollOffset
/** * @return 返回動(dòng)畫是否結(jié)束 */ public boolean computeScrollOffset (){ ... }
注釋已經(jīng)很清楚了泽西,那么接下來講一下滾動(dòng)的大概實(shí)現(xiàn)曹铃。
首先,要算出從初始位置開始滾動(dòng)捧杉,到結(jié)束的距離陕见,其實(shí)就是文字的長(zhǎng)度。
/**
* 計(jì)算滾動(dòng)的距離
* @return 滾動(dòng)的距離
*/
private int calculateScrollingLen() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
return rect.width();
}
其次味抖,調(diào)用startScroll
方法進(jìn)行滾動(dòng)评甜,注意的是需要調(diào)用invalidate
方法,才會(huì)有效果仔涩。
最后一個(gè)問題就是忍坷,滾動(dòng)結(jié)束后繼續(xù)滾動(dòng)。Scroller
在滾動(dòng)的時(shí)候,會(huì)不斷回調(diào)View
的computeScroll
方法佩研,于是就可以在這個(gè)方法里進(jìn)行判斷柑肴,如果結(jié)束了,就重新開始韧骗。
到此一個(gè)簡(jiǎn)單的跑馬燈效果就實(shí)現(xiàn)了嘉抒,當(dāng)然如果還想添加別的需要零聚,只要搞懂其原理袍暴,這些都不是問題。