Android進階——或許是處理“More&click”型多行的TextView換行的最優(yōu)雅的一種方式

引言

相信很多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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末初家,一起剝皮案震驚了整個濱河市彼念,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌井誉,老刑警劉巖赴蝇,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寥袭,死亡現(xiàn)場離奇詭異营袜,居然都是意外死亡撒顿,警方通過查閱死者的電腦和手機丑罪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門荚板,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吩屹,你說我怎么就攤上這事跪另。” “怎么了煤搜?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵免绿,是天一觀的道長。 經(jīng)常有香客問我擦盾,道長嘲驾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任迹卢,我火速辦了婚禮辽故,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腐碱。我一直安慰自己誊垢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布症见。 她就那樣靜靜地躺著喂走,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谋作。 梳的紋絲不亂的頭發(fā)上芋肠,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音遵蚜,去河邊找鬼帖池。 笑死秒咐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碘裕。 我是一名探鬼主播携取,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼帮孔!你這毒婦竟也來了雷滋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤文兢,失蹤者是張志新(化名)和其女友劉穎晤斩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姆坚,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡澳泵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兼呵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兔辅。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖击喂,靈堂內(nèi)的尸體忽然破棺而出维苔,到底是詐尸還是另有隱情,我是刑警寧澤懂昂,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布介时,位于F島的核電站,受9級特大地震影響凌彬,放射性物質(zhì)發(fā)生泄漏沸柔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一铲敛、第九天 我趴在偏房一處隱蔽的房頂上張望褐澎。 院中可真熱鬧,春花似錦原探、人聲如沸乱凿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徒蟆。三九已至,卻和暖如春型型,著一層夾襖步出監(jiān)牢的瞬間段审,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工闹蒜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寺枉,地道東北人抑淫。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像姥闪,于是被迫代替她去往敵國和親始苇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,755評論 22 665
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,930評論 6 472
  • 我沒有想過要你的指紋做什么筐喳,我只是想看看你的反應(yīng)催式。 Abel到最后一刻才說出這樣的話,讓陳冉有些吃...
    黛兒閱讀 819評論 0 0
  • 特點 專門用來在主線程上調(diào)度任務(wù)的隊列 不會開啟線程 以先進先出的方式避归,在主線程空閑時才會調(diào)度隊列中的任務(wù)在主線程...
    liu_bo閱讀 428評論 0 0