Android富文本處理

先上效果圖

效果圖.gif

一豌研、初識TextView富文本

接觸Android以來契讲,都不知道TextView能完成的事情原來不止于顯示文字這么簡單芯勘。一個TextView能完成的事情意想不到。這次接到導(dǎo)師布置的任務(wù)需求一句話概括就是完成類似美拍社區(qū)中装蓬,我的關(guān)注模塊中列表里面酷酷的文本顯示著拭。

Span的基本使用

XxxSpan span = new XxxSpan();
SpannableString spannableString  = new SpannableString("hello world");
spannableString.setSpan(span,0,spannableString.length/2,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);

上面的四行代碼是Span最基本的使用,可以解決一些簡單的需求牍帚。例如要設(shè)置某些文字為某種顏色儡遮,區(qū)別于其他文字就是new一個ForegroundSpan并且在構(gòu)造函數(shù)中傳入相應(yīng)顏色。

具體需求

要求對一段服務(wù)器下發(fā)的文本以及附加的文本信息對文本的顯示進(jìn)行處理暗赶。

  • 以#開頭#結(jié)尾的標(biāo)記著是一個話題鄙币。
  • 以@開頭空格結(jié)尾的為@某個人的ID。
  • 以http://開頭的url根據(jù)服務(wù)器下發(fā)的附加文本信息將其替換為自定義的樣式蹂随。
  • 以上三點要求點擊的時候要實現(xiàn)二態(tài)的效果十嘿。

思路分析

理清思路,要實現(xiàn)功能岳锁,大致可以分為如下的兩個個大的方面:

  • 一是處理View層面绩衷。對于文本樣式、以及點擊的時候的兩種狀態(tài)的變化屬于界面上的變化激率,在TextView上面要做成酷炫的界面效果咳燕,就得依賴于Span。
  • 二是數(shù)據(jù)的處理乒躺,處理后在界面呈現(xiàn)迟郎。

二、面向接口的嘗試聪蘸。

在處理界面的時候,樣式的實現(xiàn)就像上面Span的基本使用一樣,比較簡單健爬。對于我媒至,難到我的地方在于點擊的時候谴蔑,兩種狀態(tài)的變換 ,嘗試了兩種方法。

方法一:多個Span的疊加處理

因為Span不會有排他性崇呵,所以在開始自己的思路很容易就想到了,在原本已經(jīng)設(shè)置好的各個樣式的基礎(chǔ)之上添加一個ClickableSpan蛮瞄,繼承LinkMovementMethod并重寫onTouchEvent浴井。在onTouchEvent中處理事件,當(dāng)ACTION_DOWN的事件中添加一個背景樣式纳胧,ACTION_UP和ACTION_CANCEL的事件中移除添加的背景樣式镰吆。這樣就可以實現(xiàn)點擊時兩種狀態(tài)效果的切換。

上述的方法跑慕,可以達(dá)到相應(yīng)的效果万皿。但是考慮代碼的擴(kuò)展性,這樣做是很不好的核行。每當(dāng)增加擴(kuò)展一種樣式都要去變動onTouchEvent方法中代碼牢硅,無形中可能會造成后期更改導(dǎo)致之前已經(jīng)通過測試的代碼出現(xiàn)bug。違反了開閉原則芝雪。

方法二:定義接口减余,讓Span完成一些該完成的事情

在接觸自定義View的時候,知道自定義View最重要的三點就是:繪制惩系、布局位岔、事件邏輯處理。而這里要完成的富文本不會涉及到布局蛆挫。所以從繪制和事件處理兩方面考慮赃承,會得到一些啟發(fā)。當(dāng)ACTION_DOWN事件的時候悴侵,改變一下Span的背景顏色瞧剖,并通知重繪。ACTION_UP和ACTION_CANCEL將Span的背景顏色更改回原來的顏色可免,并通知重繪抓于。這種做法就能夠達(dá)到兩種狀態(tài)的切換。

進(jìn)而可以抽象出一個可點擊Span都要遵循的規(guī)范的接口:

public interface ITouchSpan {
    boolean onTouchDown(TextView widget);
    boolean onTouchUp(TextView widget);
    boolean onTouchCancel(TextView widget);
}

自定義的Span實現(xiàn)改接口之后浇借,override上面三個方法捉撮,增加一些自己的邏輯,在調(diào)用TextView的invalidate()方法就能達(dá)到相應(yīng)的點擊效果妇垢。

在LinkMovementMethod的onTouchEvent方法中可以這樣處理

 ITouchSpan[] link = buffer.getSpans(off, off, ITouchSpan.class);
        if (action == MotionEvent.ACTION_DOWN) {
            if (link.length != 0) {
                mTouchSpan = link[0];
                mTouchSpan.onTouchDown(widget);
            }
        } else if (action == MotionEvent.ACTION_UP) {
            if (mTouchSpan != null) {
                mTouchSpan.onTouchUp(widget);
                mTouchSpan = null;
            }

        } else if (action == MotionEvent.ACTION_CANCEL) {
            if (mTouchSpan != null) {
                mTouchSpan.onTouchCancel(widget);
                mTouchSpan = null;
            }
        }

在onTouchEvent()方法中只需要對接口的引用進(jìn)行處理巾遭,而不涉及具體的實現(xiàn)肉康。當(dāng)我們需要擴(kuò)展一個新的樣式,并要求該樣式可點擊灼舍,點擊的時候要有某種特定的效果吼和。我們只擴(kuò)展一個類實現(xiàn)ITouchSpan,在相應(yīng)的方法中實現(xiàn)即可擴(kuò)展骑素。好處在于增加一種新的樣式并不需要太多的更改onTouchEvent()的方法炫乓,只需要增加一個樣式類并實現(xiàn)ITouchSpan接口就可以達(dá)到效果。

三献丑、數(shù)據(jù)處理的抽離

數(shù)據(jù)邏輯的處理大致分為三個部分:

  • 正則匹配服務(wù)器下發(fā)的一段文本信息末捣。記錄下三種樣式的起始位置。因為每種樣式的個數(shù)是不確定的创橄,所以要用一個列表進(jìn)行存儲樣式的起始位置箩做。
  • 根據(jù)位置列表,設(shè)置相應(yīng)的Span
  • 最后一個是一個小細(xì)節(jié)筐摘。根據(jù)icon圖標(biāo)的url地址去下載相應(yīng)的icon圖標(biāo)卒茬。

剛開始處理數(shù)據(jù)主要是在Adapter中定義三個方法,onBindViewHolder調(diào)用這三個方法分別處理三種樣式咖熟。類似第二點圃酵,每當(dāng)新增一個樣式的時候,都需要在Adapter中增加一個方法馍管,這樣做就會使得Adapter這個類要做的事情越來越多郭赐,功能也越來越復(fù)雜。不符合單一職責(zé)原則确沸。

換一種方式思考捌锭,樣式數(shù)據(jù)邏輯處理交由某個類幫忙處理,Adapter只需要調(diào)用該類對象的方法即可 這個思路就可以做到較好的將adapter里面復(fù)雜且冗余的數(shù)據(jù)處理進(jìn)行抽離罗捎。

具體的解決方法:

定義一個接口观谦,規(guī)定了數(shù)據(jù)綁定的方法。每當(dāng)新增加一個樣式桨菜,要處理該樣式的數(shù)據(jù)豁状,只需要實現(xiàn)改接口,就能夠處理數(shù)據(jù)邏輯倒得。

public interface IBindStyleListStrategy {
    void bindStyleList(TextView textView, Spannable text, Comment comment, Context context);
}

Adapter中根據(jù)相應(yīng)的策略的具體實現(xiàn)類去綁定具體的樣式泻红。

 //根據(jù)樣式的類型設(shè)置樣式
        for (int i = 0; i < BindStyleListStrategyFactory.STYLE_COUNT; i++) {
            iBindStyleListStrategy = BindStyleListStrategyFactory.getBindStyleList(i);
            if (iBindStyleListStrategy != null) {
                //綁定樣式
                iBindStyleListStrategy.bindStyleList(holder.textView, text, comment, holder.itemView.getContext());
            }
        }
        //顯示數(shù)據(jù)
        holder.textView.setText(text);

BindStyleListStrategyFactory的具體實現(xiàn)

public class BindStyleListStrategyFactory {
    //樣式種類個數(shù)
    public static final int STYLE_COUNT = 3;
    //話題類型
    public static final int TOPIC_TYPE = 0;
    //URL鏈接類型
    public static final int URL_TYPE = 1;
    //AtUser類型
    public static final int AT_USER_TYPE = 2;
    //返回具體實現(xiàn)類
    public static IBindStyleListStrategy getBindStyleList(int type) {
        switch (type) {
            case TOPIC_TYPE:
                return new BindTopicListStrategy();
            case AT_USER_TYPE:
                return new BindAtUserListStrategy();
            case URL_TYPE:
                return new BindLinkUrlListStrategy();
        }
        return null;
    }
}

上述的解決辦法,在增加一個樣式類型的時候霞掺。只需要定義一個類實現(xiàn)IBindStyleListStrategy接口并實現(xiàn)對應(yīng)的方法谊路,在BindStyleListStrategyFactory中修改樣式種類個數(shù),以及增加返回具體的類型的對象即可擴(kuò)展樣式菩彬,而不需要每增加一個樣式類型改動adapter中的代碼缠劝。

四潮梯、代碼的迭代與收獲總結(jié)

細(xì)節(jié)處理總結(jié)

  1. 在某些細(xì)節(jié)處理,可能剛開始做的沒有很到位剩彬。例如剛開始的點擊事件處理的代碼

 SpanUtils.setForegroundColorSpan(text, color, atUser.getStart(), atUser.getEnd());
            SpanUtils.setClickSpan(text, atUser.getStart(), atUser.getEnd(), atUser,
                    new SpanUtils.SpanClickListener() {
                        @Override
                        public void onClick(Object o) {
                            AtUser atUser = (AtUser) o;
                            Intent intent = new Intent(mActivity, SecondActivity.class);
                            intent.putExtra("text", atUser.getUrl());
                            mActivity.startActivity(intent);
                        }
                    });
        }

上面的代碼每當(dāng)設(shè)置一個Span就要new一個回調(diào)接口的對象酷麦,而其實new出來的對象做的事情都是類似的,權(quán)衡之下喉恋,如果將事件的處理放到Span內(nèi)部并結(jié)合上面的onTouchUp(TextView widget);方法中處理,可以少寫很多重復(fù)的代碼母廷。

  1. 圖片的加載

    在處理鏈接樣式的時候轻黑,需要根據(jù)url地址去加載網(wǎng)絡(luò)上面的圖標(biāo)。我學(xué)習(xí)了Glide的基本使用的方法琴昆,在Glide提供的回調(diào)接口中獲取圖片的Bitmap對象氓鄙。得到Bitmap之后,將其設(shè)置給相應(yīng)的Span并通知TextView重繪业舍。剛開始處理的時候抖拦,每加載一張圖片就重繪一次。如果采取這種方式舷暮,會導(dǎo)致TextView被重繪多次态罪,性能不夠好。更好的方式是全部的圖片加載完成之后下面,再進(jìn)行一次重繪复颈。解決方法如下:

         /**
         * 檢查一個TextView中所有的鏈接的Icon圖標(biāo)是否全部加載完成
         *
         * @param linkUrlSpanList Span列表
         * @return 返回是否全部加載完成
         */
     private boolean checkAllLoadSuccess(List<LinkUrlSpan> linkUrlSpanList) {
            boolean loadAll = true;
            for (LinkUrlSpan linkUrlSpan : linkUrlSpanList) {
                loadAll = linkUrlSpan.isLoadSuccess() && loadAll;
            }
            return loadAll;
        }
    

總結(jié)

通過這次Demo的練習(xí),對接口的使用沥割,設(shè)計模式兩個基本原則:開閉原則耗啦、單一職責(zé)原則,簡單工廠模式机杜、策略模式有了更深的理解帜讲。其次是學(xué)習(xí)了Glide的基本使用。在自定義Span實現(xiàn)鏈接的樣式的那一塊椒拗,在學(xué)習(xí)自定義view的時候有一定的了解似将,所以直接在draw方法中進(jìn)行繪制,完成較快陡叠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玩郊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枉阵,更是在濱河造成了極大的恐慌译红,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兴溜,死亡現(xiàn)場離奇詭異侦厚,居然都是意外死亡耻陕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門刨沦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诗宣,“玉大人,你說我怎么就攤上這事想诅≌倥樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵来破,是天一觀的道長篮灼。 經(jīng)常有香客問我,道長徘禁,這世上最難降的妖魔是什么诅诱? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮送朱,結(jié)果婚禮上娘荡,老公的妹妹穿的比我還像新娘。我一直安慰自己驶沼,他們只是感情好炮沐,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著商乎,像睡著了一般央拖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹉戚,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天鲜戒,我揣著相機(jī)與錄音,去河邊找鬼抹凳。 笑死遏餐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赢底。 我是一名探鬼主播失都,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幸冻!你這毒婦竟也來了粹庞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洽损,失蹤者是張志新(化名)和其女友劉穎庞溜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碑定,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡流码,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年又官,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫试。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡六敬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驾荣,到底是詐尸還是另有隱情外构,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布秘车,位于F島的核電站典勇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏叮趴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一权烧、第九天 我趴在偏房一處隱蔽的房頂上張望眯亦。 院中可真熱鬧,春花似錦般码、人聲如沸妻率。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宫静。三九已至,卻和暖如春券时,著一層夾襖步出監(jiān)牢的瞬間孤里,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工橘洞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留捌袜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓炸枣,卻偏偏與公主長得像虏等,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子适肠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,766評論 22 665
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理霍衫,服務(wù)發(fā)現(xiàn),斷路器侯养,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程敦跌,因...
    小菜c閱讀 6,424評論 0 17
  • 我用年輕的手指扣響門扉 你的擁抱是語言和語言的碰撞 我有永不疲倦的心臟 你有渴求私語的愿望 于是我們枯萎 像是失去...
    愛佳文寒閱讀 207評論 1 4