引言
相信很多Android APP 開發(fā)者在處理TextView 換行的時候都曾頭痛不已過,尤其是在做復(fù)雜布局的時候蝶怔,適配的時候都踩過不少坑。筆者也踩過,直到在一次查看源碼的時候發(fā)現(xiàn)了ViewTreeObserver宾符,總算是實現(xiàn)了優(yōu)雅的格式化多行文本,在使用一個控件的時候抽點時間了解下提供的公共方法灭翔,有時候可以避免很多不必要的坑魏烫。
一、ViewTreeObserver概述
ViewTreeObserver顧名思義就是視圖樹的觀察者角色肝箱,可以監(jiān)聽視圖樹的全局變化哄褒,比如整棵樹的布局,開始的繪畫傳遞煌张,觸摸模式的改變等等呐赡,都提供了對應(yīng)的八個監(jiān)聽接口(A view tree observer is used to register listeners that can be notified of global changes in the view tree)。ViewTreeObserver用來注冊監(jiān)聽器骏融,在視圖樹全局發(fā)生變化時收到通知链嘀。它不能被應(yīng)用實例化,因為它是由視圖提供档玻,只能通過調(diào)用android.view.View的getViewTreeObserver()來獲取對應(yīng)的實例怀泊。
其實整個ViewTreeObserver機制從源碼上看,本質(zhì)上就是個觀察者模式误趴,那么主要的角色就有兩種:
ViewTree視圖樹——在Android中所有視圖由View和View的子類組成霹琼。ViewGroup也是view的子類,它是View的容器凉当,它可以裝載View和ViewGroup枣申。這樣ViewGroup和View以樹形結(jié)構(gòu)一層一層的嵌套組合,就形成了視圖樹纤怒。
Observer觀察者糯而。使用了觀察者的設(shè)計模式,ViewTree是被觀察者(或者說是主題泊窘、內(nèi)容)熄驼,ViewTreeObserver是觀察者像寒,通過ViewTreeObserver注冊監(jiān)聽來觀察ViewTree的變化,當(dāng)ViewTree發(fā)生變化瓜贾,就會調(diào)用ViewTreeObserver的相關(guān)方法來通知其這一改變诺祸。我們可以在ViewTreeObserver中add自己的監(jiān)聽器,從而得到ViewTree的某一變化的通知做出自己的邏輯處理祭芦。
二筷笨、SpannableString和ClickableSpan概述
SpannableString和ClickableSpan本質(zhì)上就是高級的String,具體可參見Android進階——借助強大Span家族增添豐富的特效及格式化字符串龟劲。
三胃夏、實現(xiàn)思路
簡單來說就是實現(xiàn)根據(jù)TextView要實現(xiàn)的字符串長度去動態(tài)適配。
通過ViewTreeObserver機制監(jiān)聽昌跌,TextView繪制之后仰禀,在OnGlobalLayoutListener中計算原始的字符串長度,當(dāng)多行顯示的時候蚕愤,以第一行所顯示字符的個數(shù)為行長答恶。
判斷最后一行的長度,并進行邏輯處理萍诱,添加“More”型字符串
以Span系為輔助實現(xiàn)點擊悬嗓,并開放接口
四、實現(xiàn)源碼
核心源碼
package com.crazyview.loadertest;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.TextView;
/**
* Auther: Crazy.Mo
* DateTime: 2017/11/14 10:55
* Summary:
*/
public class FormatUtil {
/**
* @param textView 目標(biāo)TextView
* @param moreStr more型字符串裕坊,當(dāng)顯示不完全的時候顯示替代字符串
* @param clickListener 點擊的回調(diào)接口
*/
public static void getTextMaxEms(final TextView textView, final String moreStr, final LinkClickListener clickListener){
final String contentStr=textView.getText().toString();
ViewTreeObserver viewTreeObserver=textView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
@Override
public void onGlobalLayout() {
if(textView.getTag()==null){
textView.setTag(textView.getText().toString());
}
String currentStr=textView.getText().toString();
ViewTreeObserver treeObserver=textView.getViewTreeObserver();
treeObserver.removeOnGlobalLayoutListener(this);
int lineCount=textView.getLineCount();
if(lineCount>1) {
//獲取第一行的文本長度當(dāng)做每行文本的長度
int lineLength = textView.getText().subSequence(textView.getLayout().getLineStart(0), textView.getLayout().getLineEnd(0)).toString().length();
//獲取最后一行文本的長度
int lastLineLength = textView.getText().subSequence(textView.getLayout().getLineStart(textView.getLayout().getLineCount() - 1), textView.getLayout().getLineEnd(textView.getLayout().getLineCount() - 1)).toString().length();
if (lastLineLength >= lineLength - moreStr.length() - 2) {
currentStr = currentStr.substring(0, contentStr.length() - (lastLineLength - (lineLength - moreStr.length() - 5))) + "...";
}
final String finalStr = currentStr + moreStr;
SpannableString spanString = new SpannableString(finalStr);
ClickableSpan clickSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
clickListener.onLinkClick(contentStr);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(ds.linkColor);
ds.setUnderlineText(true);
}
};
spanString.setSpan(clickSpan, currentStr.length(), finalStr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spanString);
textView.setLinkTextColor(Color.RED);
//必須添加這一段
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setFocusable(false);
textView.setClickable(false);
textView.setLongClickable(false);
}
}
});
}
}
點擊回調(diào)接口
package com.crazyview.loadertest;
/**
* Auther: Crazy.Mo
* DateTime: 2017/11/14 14:52
* Summary:
*/
public interface LinkClickListener {
void onLinkClick(Object object);
}
使用
package com.crazyview.loadertest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
public class MultableTextActivity extends AppCompatActivity {
private TextView textOneline,textView,textMult,textlastLine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multable_text);
init();
}
private void init() {
textView= (TextView) findViewById(R.id.tv_single);
textView.setText("百世快遞投遞員于2017年10月14日下午四點左右");
FormatUtil.getTextMaxEms(textView, "查看詳情", /*textView.getText().toString(),*/ new LinkClickListener() {
@Override
public void onLinkClick(Object object) {
Toast.makeText(MultableTextActivity.this,"查看詳情Click"+(String)object,Toast.LENGTH_SHORT).show();
}
});
textOneline= (TextView) findViewById(R.id.tv_oneline);
textOneline.setText("百世快遞投遞員于2017年10月14日下午四點左右到我處成");
FormatUtil.getTextMaxEms(textOneline, "查看詳情", /*textOneline.getText().toString(),*/ new LinkClickListener() {
@Override
public void onLinkClick(Object object) {
Toast.makeText(MultableTextActivity.this,"查看詳情Click"+(String)object,Toast.LENGTH_SHORT).show();
}
});
textMult= (TextView) findViewById(R.id.tv_mutlline);
textMult.setText("百世快遞投遞員于2017年10月14日下午四點左右到我處成功攬件包竹,直至今日2017年10月30日收件人還未收到包裹并且也無法在對方的網(wǎng)上查詢到有關(guān)包裹的任何消息,曾經(jīng)有一次去代辦點領(lǐng)取包裹親眼目睹包裹的胡亂拋放碍庵,于是在此期間多次聯(lián)系對方客服映企,詢問包裹情況,對方客服也數(shù)次明說24小時內(nèi)會給一個反饋静浴,由于時間久遠只記得部分客服工號(LYWX035)堰氓,但是24小時、48小時甚至72小時都無任何回復(fù)苹享,由于此包裹所寄物品是從香港買回來的藥双絮,北京我家人急用已經(jīng)嚴重延誤了,懇請總局幫忙聯(lián)系無恥百世快遞得问,并請求賠償原物品及1元精神損失囤攀。");
FormatUtil.getTextMaxEms(textMult, "查看詳情", /*textMult.getText().toString(),*/ new LinkClickListener() {
@Override
public void onLinkClick(Object object) {
Toast.makeText(MultableTextActivity.this,"查看詳情Click"+(String)object,Toast.LENGTH_SHORT).show();
}
});
textlastLine= (TextView) findViewById(R.id.tv_lastline);
textlastLine.setText("百世快遞投遞員于2017年10月14日下午四點左右到我處成功攬件,直至今日2017年10月30日收件人還未收到包裹并且也無法在對方的網(wǎng)上查詢到有關(guān)包裹的任何消息宫纬,曾經(jīng)有一次去代辦點領(lǐng)取包裹親眼目睹包裹的胡亂拋放焚挠,于是在此期間多次聯(lián)系對方客服,詢問包裹情況漓骚,對方客服也數(shù)次明說24小時內(nèi)會給一個反饋蝌衔,由于時間久遠只記得部分客服工號(LYWX035)榛泛,但是24小時、48小時甚至72小時都無任何回復(fù)噩斟,由于此包裹所寄物品是從香港買回來的藥曹锨,北京我家人急用已經(jīng)嚴重延誤了帽揪,懇請總局幫忙聯(lián)系無恥百世快遞屡江,并請");
FormatUtil.getTextMaxEms(textlastLine, "查看詳情", /*textlastLine.getText().toString(),*/ new LinkClickListener() {
@Override
public void onLinkClick(Object object) {
Toast.makeText(MultableTextActivity.this,"查看詳情Click"+(String)object,Toast.LENGTH_SHORT).show();
}
});
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="顯示文字長度不夠一行時:"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_single"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="顯示文字長度剛好一行時:"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_oneline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多行最后一行>=行長度減去more型再減去2"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_mutlline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多行最后一行小于行長度減去more型再減去2"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_lastline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>