這篇文章能夠幫你實現下面的效果。末尾固定顯示「查看更多」,或者任何你自定義的文字,並且高效實現了一個展開摺疊動畫。 java
效果動圖參考這裏: github.com/aesean/Acti…不關心原理,只須要實現的參考這裏: TextViewExtensionsgit
使用示例: ShowMoreAnimationActivity,或者參考下面的使用示例github
Kotlin用法算法
TextViewSuffixWrapper(textView).apply wrapper@{
this.mainContent = getString(R.string.sample_text)
this.suffix = "...查看更多"
this.suffix?.apply {
suffixColor("...".length, this.length, R.color.md_blue_500, listener = View.OnClickListener { view ->
toast("click ${this}")
})
}
this.transition?.duration = 5000
sceneRoot = this.textView.parent.parent.parent as ViewGroup
collapse(false)
this.textView.setOnClickListener {
toast("click view")
this@wrapper.toggle()
}
}
複製代碼
Java版用法app
TextViewSuffixWrapper wrapper = new TextViewSuffixWrapper(textView);
wrapper.setMainContent("mainContent");
String suffix = "...查看更多";
wrapper.setSuffix(suffix);
wrapper.suffixColor("...".length(), suffix.length(), R.color.colorAccent, new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
wrapper.collapse(false);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
wrapper.toggle();
}
});
複製代碼
Android的TextView支持最大行數,好比你但願一個TextView最大行數是2行。ide
textView.maxLines = 2
複製代碼
這時候textView最多顯示兩行。這時候你能夠經過設置ellipsize,讓文字超過兩行的時候用「...」代替最後幾個字。設置以後,TextView依然只有兩行。性能
textView.maxLines = 2
textView.ellipsize = TextUtils.TruncateAt.END
複製代碼
那問題來了,我以爲...
的提示不夠明顯,我想換成相似查看更多
這樣的文字怎麼辦?並且我但願給查看更多加上顏色,怎麼辦?好比顯示成文章開頭第一個圖。這個需求其實仍是很是常見的,Android原生並提供相似的實現,那咱們只能本身動手了。優化
咱們用下面的文本做爲textView要顯示的文本,咱們把這段文字取個名字叫:mainContent
動畫
"歐陽峯(獨白):離開白駝山以後,我去了這個沙漠,開始了另外一種生活。歐陽峯(獨白):初六日,驚蟄。每一年這個時候,都會有一我的來找我喝酒,他的名字叫黃藥師。這我的很奇怪,每次總從東邊而來,這習慣已經維持了好多年。今年,他給我帶了一份手信。黃藥師:不久前,我趕上一我的,送給我一罈酒,她說那叫"醉生夢死",喝了以後,能夠叫你忘掉以作過的任何事。我很奇怪,爲何會有這樣的酒。她說人最大的煩惱,就是記性太好,若是什麼均可以忘掉,之後的每一天將會是一個新的開始,那你說這有多開心。這壇酒原本打算送給你的,看起來,咱們要分來喝了。歐陽峯(獨白):對於太古怪的東西,我向來很難接受,因此這壇"醉生夢死"我一直沒有喝。可能這酒真的有效,從那天晚上開始,黃藥師開始忘記了不少事情。ui
要顯示成下面這個圖的效果,咱們能夠把問題拆成兩部分。
mainContent
中找選取多少個字的時候,後面加上...查看更多
能夠恰好是兩行,並且再多加一個字都會致使後面加上...查看更多
會換行。查看更多
的顏色爲藍色問題已經被抽象出來了。先看第二個問題,改變文字顏色,這個簡單,咱們經過SpannableStringBuilder
配合ForegroundColorSpan
很容易實現。但第一個問題,找前面到底放多少個字,加上後面的後綴恰好能夠放兩行,這個怎麼處理?
假如咱們有個方法能知道放若干個字後的TextView行數,那咱們假如從mainContent
裏找到這樣一個index,可以知足下面的條件
textView.text = mainContent.substring(0, index) + "...查看更多"
// textView.lineCount == 2
textView.text = mainContent.substring(0, index+1) + "...查看更多"
// textView.lineCount == 3
複製代碼
(0, index)放到TextView,textView行數是2,(0, index+1)放到TextView,textView行數是3。那麼這個index其實就是咱們要到的mainContent
應該截斷的位置。少一個字或者多一個字都不對。那TextView有提供獲取行數的方法嗎?
有提供,咱們能夠經過下面的方法
textView.text = "111"
val lineCount = textView.layout.lineCount // lineCount == 1
複製代碼
並且這個方法能夠同步獲取到lineCount,簡單說就是隻要能獲取到layout,那麼setText後能夠馬上讀取到lineCount,不須要等待下一次消息循環。那麼咱們能夠簡單的經過下面的代碼找到恰好能放下的index。
fun findIndex(textView: TextView, mainContent: CharSequence, suffix: CharSequence): Int {
for (i in mainContent.length downTo 1) {
textView.text = mainContent.subSequence(0, i).toString() + suffix.toString()
val lineCount0 = textView.layout.lineCount
textView.text = mainContent.subSequence(0, i + 1).toString() + suffix.toString()
val lineCount1 = textView.layout.lineCount
if (lineCount0 == 2 && lineCount1 == 3) {
return i
}
}
return -1
}
複製代碼
邏輯很是簡單,一個個去試,直到找到多一個都放不下的index。
到這裏已經能夠很是粗略的實現出這樣效果的demo了,但實際使用還會有很是多問題,好比layout並非總能拿到,onCreate中setText後拿到的layout是null,須要等到TextView的onMeasure以後才能生成layout。另外layout過程是很消耗資源的,我能不能把layout過程放在子線程?還有這一個個文本的嘗試實在是有點蠢,有沒有更優化的算法?
查看TextView源碼,咱們能夠發現layout第一次被建立是在onMeasure裏,那咱們在onCreate裏setText後固然是拿不到Layout的,那怎麼辦?個人作法是判斷到當Layout爲null的時候添加LayoutChangeListener,在LayoutChangeListener中從新拿Layout。
若是你用過新TextView的autoTextSize,若是用過的話,你可能會發現這個功能在AppCompatTextView中作了低版本兼容實現。裏面有個類叫AppCompatTextViewAutoSizeHelper
,經過反射等一系列方法,新建立了一個StaticLayout出來,而後經過新建立的這個StaticLayout配合二分查找來計算最合適的文字大小。
那咱們能不能模仿AppCompatTextViewAutoSizeHelper
的實現,咱們也new一個StaticLayout出來?new一個StaticLayout出來沒什麼問題,但問題是,TextView並不老是使用StaticLayout來layout文本,好比咱們須要改變查看更多
的顏色,同時須要能點擊查看更多
,那麼咱們會用到SpannableString,這時候TextView會使用DynamicLayout來layout文本,不一樣的Layout得出的layout結果有多是不一樣的。那麼咱們就須要不光new一個StaticLayout,還須要在合適的時候new一個DynamicLayout出來,這一系列的緣由會讓這個問題複雜化。最後我放棄了new一個新DynamicLayout出來。而是直接利用TextView本身的layout,這樣能大大下降問題複雜性,但帶來的問題就是不能在子線程測量,同時因爲會屢次setText,那麼就會致使由於屢次TextChangedListener回調。
優化很簡單,直接二分查找。二分的過程仍是比較簡單的,具體如何二分這裏不詳細介紹了,能夠在logcat中過濾關鍵字TextViewLayout
,在debug級別的日誌中能夠看到整個二分查找過程。
既然解決了如何摺疊,那麼你可能也會須要展開。展開很是簡單,直接把全部文本設置上去就能夠了。若是須要加動畫怎麼辦?能夠用Animator嗎?或者Animation?
無論用Animator仍是Animation,動畫過程當中,須要改變的是什麼?是高度,但問題來了,沒動以前我怎麼知道要動到什麼高度?這是個很是棘手的問題。
好在Google給咱們提供了另一個實現動畫的方式,TransitionManager
TransitionManager.beginDelayedTransition(sceneRoot, transition)
複製代碼
sceneRoot是你但願動畫的目標View,transition是動畫方式,transition一般咱們默認使用AutoTransition就能夠了。那爲啥TransitionManager能這麼動畫呢?主要是下面一行代碼
sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener)
複製代碼
這個動畫性能很是好,它在改變高度寬度等動畫的時候,只觸發一次layout,能夠大大下降性能消耗,更詳細具體的原理回頭有空了再分析。
整個過程就介紹結束了,有什麼其餘疑問歡迎給我留言評論。