鍍金池/ 教程/ Android/ 自定義顏色范圍
原文鏈接
Issue #185
Issue #181
Issue #161
Issue #192
Issue #174
Issue #190
RecyclerView FastScroll – Part 2
僅作為Android 調(diào)試模式工具的Stetho
Issue #150
Issue #167
Issue #180
Issue #151
Issue #188
Issue #159
Issue #189
Issue #160
Issue #168
Issue #146
Issue #173
Issue #198
Issue #179
延期的共享元素轉(zhuǎn)換(3b)
Yahnac:RxJava Firebase&內(nèi)容提供
Issue #162
游戲性能:規(guī)劃限定條件
分析清單:測(cè)量和尋找哪些方面
Issue #148
Issue #166
Issue #158
Issue #178
Issue #193
Issue #145
Issue #170
Issue #169
Issue #196
Issue #186
Issue #172
Issue #171
附加Android工件和Gradle的檔案
Issue #147
自定義顏色范圍
根據(jù) Material 設(shè)計(jì)導(dǎo)航制圖工具樣式
Issue #187
Issue #184
Issue #175
在Android Lollipop上使用JobScheduler API
Android性能案例追蹤研究
使用安卓Wear API創(chuàng)建watchface—第2部分
在谷歌市場(chǎng)上創(chuàng)造更好的用戶體驗(yàn)
映射與包的神秘關(guān)系
Issue #165
用Robolectric進(jìn)行參數(shù)化測(cè)試
Issue #155
Issue #149
MVC / MVP中的M -模型
歡迎為 Android 和 iOS 嵌入 API
Issue #164
Android UI 自動(dòng)化測(cè)試
Issue #182
Issue #191
Issue #183
Issue #163
Issue #157
響應(yīng)式編程(Reactive Programming)介紹
Issue #197
原文鏈接
Issue #153
Issue #152
Issue #176
原文地址
Android Material 支持庫(kù):Electric Boogaloo的提示與技巧
Issue #156
Issue #154
Android的模糊視圖
Issue #194
Issue #177
Issue #195
針對(duì)Jenkins的谷歌商店安卓出版插件

自定義顏色范圍

《Styling Android》的忠實(shí)讀者應(yīng)該知道我是一個(gè)Spans的骨灰級(jí)粉絲,知道我認(rèn)為掌握好Span對(duì)于發(fā)揮TextView的最大功效來說至關(guān)重要。人們說Span有時(shí)只做一些簡(jiǎn)單的工作,比如僅僅改變文本的顏色,這樣看起來有一點(diǎn)尷尬。這本文中,我們將展望如何利用我們自己的Span,并且看一下使用自定義Span可進(jìn)行如何的簡(jiǎn)化。

在我們開始前,我要先簡(jiǎn)短地介紹一下。編寫本文的靈感來自于一些代碼,這些代碼使用一個(gè)我最不喜歡的類別即android.text.Html中執(zhí)行的文本樣式。在這個(gè)類別在一個(gè)文本字符串中解析html標(biāo)記并生成相關(guān)的Span后,其本身就不是問題了。然而,我碰到的問題時(shí)是這個(gè)類別支持難以維護(hù)代碼。使用Html的代碼常常需要進(jìn)行重寫才可以直接利用使用Span的功能以滿足做出更改的需要。

一個(gè)示例可能會(huì)幫助闡明這個(gè)問題。想一想TextView文本的一部分需要在剩下的字符串中采用不同顏色的情況。這個(gè)任務(wù)可以使用Html類別來完成:

Spanned text = Html.fromHtml("One word should be <font color='16711680'>red</font>");
textView.setText(text);

我首先遇到的麻煩是它看起來很不臃腫。Java代碼中字符串的嵌入式HTML標(biāo)記是一個(gè)看起來簡(jiǎn)單的代碼,這是因?yàn)橛泻芏嗫尚械墓ぞ弑绕涓行?。雖然字符串可以移入一個(gè)字符串資源中,但如果在運(yùn)行時(shí)需要對(duì)實(shí)際顏色值進(jìn)行確定,這種情況則通常不會(huì)發(fā)生。 我們最后必須執(zhí)行甚至更為臃腫的字符串連接,或我們需要使用格式化的字符串資源來生成適當(dāng)?shù)腍TML字符串,之后我們使用Html類別對(duì)其進(jìn)行解析。還有一個(gè)更大的麻煩就是Html的fromHtml()方法可使我們?cè)跇?biāo)記內(nèi)指定顏色資源,包括顏色狀態(tài)列表。我們只能在‘a(chǎn)ndroid’包中找到它,所以我們無法介入系統(tǒng)資源,并不能使用我們自己的資源。 如果我們?cè)谏蠈覶extView更改的情況下需要改變文本顏色,這就會(huì)給我們帶來問題。

我已經(jīng)對(duì)一個(gè)Html為什么不好的用例進(jìn)行了闡述,那么應(yīng)如何解決這個(gè)問題呢? 使用Span來更改文本的通常的方法是使用TextAppearanceSpan,但是這常常需要我們指定其他的因素,比如文本外觀或字體等。 在上述列子中,我們僅關(guān)注了改變一部分字符串字體顏色的問題,所以大可不必使用TextAppearanceSpan。其他的選擇還有 ForegroundColorSpan,但這個(gè)方法同樣存在著不支持顏色狀態(tài)列表的問題。

但是我們可以輕松地創(chuàng)建我們自己的自定義Span,它可以準(zhǔn)確地完成我們需要做的工作(而且我們可以在此之后在整個(gè)應(yīng)用程序中再次使用這個(gè)自定義Span)。

讓我們首先建立一個(gè)簡(jiǎn)單的自定義Span,其將僅僅改變文本的顏色。我們?cè)谶@里可以改用ForegroundColorSpan(而且我肯定會(huì)使用生產(chǎn)代碼),但是我選擇創(chuàng)建一個(gè)自定義的代碼,僅此用來幫助解釋如何編寫我們自己的Span:

class StaticColourSpan extends CharacterStyle {
    private final int colour;

    public StaticColourSpan(int colour) {
        super();
        this.colour = colour;
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.setColor(colour);
    }
}

很簡(jiǎn)單吧? 構(gòu)造函數(shù)使用了一個(gè)顏色值,因?yàn)槲覀償U(kuò)展了CharacterStyle,所以我們需要使用updateDrawState()。這種方法將在文本的onDraw()前調(diào)用,并允許我們調(diào)整Paint繪畫對(duì)象,以此給文本上色。所以,我們所有需要做的是設(shè)置繪畫對(duì)象的顏色,之后就全部搞定了。

現(xiàn)在一些人可能已經(jīng)意識(shí)到這種方法并不能解決TextView狀態(tài)變化下文本更改顏色用例的問題,但我們可以創(chuàng)建另一種可以精確完成這個(gè)工作的Span。

class ColourStateListSpan extends CharacterStyle {
    private final ColorStateList colorStateList;

    public ColourStateListSpan(ColorStateList colorStateList) {
        super();
        this.colorStateList = colorStateList;
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.setColor(colorStateList.getColorForState(tp.drawableState, 0));
    }
}

這是非常相似的,差異是構(gòu)造函數(shù)采用了ColorStateList而不是原始的顏色值,而且在updateDrawSate(),我們?cè)贑olorStateList(顏色狀態(tài)列表)中查找了合適的顏色,這些顏色依賴我們從TextPaint對(duì)象獲得的狀態(tài)。updateDrawState() 將在TextView 刷新后調(diào)出,所以我們?cè)诖藭r(shí)可獲知該控制狀態(tài)。

下一個(gè)考慮的事情就是我們真的不希望加載ColorStateList對(duì)象將其調(diào)出。 但是我們可以輕松地創(chuàng)建一個(gè)工廠方法,這個(gè)方法將采取資源標(biāo)識(shí)符并依靠資源類型加載適合的Span。

public abstract class TextColourSpan extends CharacterStyle {
    public static TextColourSpan newInstance(Context context, int resourceId) {
        Resources resources = context.getResources();
        ColorStateList colorStateList = resources.getColorStateList(resourceId);
        if (colorStateList != null) {
            return new ColourStateListSpan(colorStateList);
        }
        int colour = resources.getColor(resourceId);
        if (colour >= 0) {
            return new StaticColourSpan(colour);
        }
        return null;
    }
}

如果我們改變StaticColourSpan和 ColourStateListSpan類別,從而擴(kuò)展基礎(chǔ)類別,而不是直接擴(kuò)展CharacterStyle,那么我們有了一個(gè)多態(tài)的newInstance()方法,這個(gè)方法將依靠加載資源的類型而返回合適的對(duì)象。

最后一件值得考慮的事就是如何確定Span應(yīng)用在字符串的范圍。字符串模式匹配的一個(gè)顯而易見的選擇是使用正則表達(dá)式,研究一個(gè)實(shí)用類別是如何為我們完成這個(gè)工作的。

public final class SpanUtils {
    private SpanUtils() {
    }

    public static CharSequence createSpannable(Context context, int stringId, Pattern pattern, CharacterStyle... styles) {
        String string = context.getString(stringId);
        return createSpannable(string, pattern, styles);
    }

    public static CharSequence createSpannable(CharSequence source, Pattern pattern, CharacterStyle... styles) {
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(source);
        Matcher matcher = pattern.matcher(source);
        while (matcher.find()) {
            int start = matcher.start();
            int end = matcher.end();
            applyStylesToSpannable(spannableStringBuilder, start, end, styles);
        }
        return spannableStringBuilder;
    }

    private static SpannableStringBuilder applyStylesToSpannable(SpannableStringBuilder source, int start, int end, CharacterStyle... styles) {
        for (CharacterStyle style : styles) {
            source.setSpan(CharacterStyle.wrap(style), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }
        return source;
    }
}

我們現(xiàn)在可以使用字符串將其調(diào)出,在出現(xiàn)匹配時(shí)應(yīng)用我們希望匹配的正則表達(dá)式和Span對(duì)象列表。 這種方法可輕松地隨時(shí)進(jìn)行應(yīng)用,而且比Html實(shí)施機(jī)制更為簡(jiǎn)潔。然而,可以肯定的是其在理解和維護(hù)上更為輕松。而且這個(gè)方法用起來很有效

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CharacterStyle redText = TextColourSpan.newInstance(this, R.color.bright_red);
        CharacterStyle changeText = TextColourSpan.newInstance(this, R.color.pressable_string);
        Pattern redPattern = Pattern.compile(getString(R.string.simple_string_pattern));
        Pattern changePattern = Pattern.compile(getString(R.string.pressable_string_pattern));

        final TextView text2 = (TextView) findViewById(R.id.text2);
        final TextView text3 = (TextView) findViewById(R.id.text3);

        formatUsingSpans(text2, R.string.simple_string, redPattern, redText);

        formatUsingSpans(text3, R.string.pressable_string, redPattern, redText);
        formatUsingSpans(text3, changePattern, changeText);
    }

    private void formatUsingSpans(TextView textView, int stringId, Pattern pattern, CharacterStyle... styles) {
        CharSequence text = SpanUtils.createSpannable(this, stringId, pattern, styles);
        textView.setText(text);
    }
}

附帶的源文件還有一些進(jìn)一步的例子,指出這種技術(shù)是如何應(yīng)用到這幾個(gè)簡(jiǎn)單類別的。

所以,總而言之:如果我承繼了你使用Html走了捷徑編寫的代碼,那么我將把你找出來并報(bào)仇雪恨。 然而,更有可能發(fā)生的事你將必須維護(hù)這個(gè)代碼,而且最后自怨自艾。 不要使用Html類別來創(chuàng)建技術(shù)債務(wù),應(yīng)該多用點(diǎn)心使用Spans來正確地完成任務(wù)。

本文中的源代碼在可見。

上一篇:Issue #193下一篇:Issue #152