【圖像格式篇】可以從網(wǎng)絡(luò)加載點(diǎn)9圖的嗎概耻?

從網(wǎng)絡(luò)加載點(diǎn)9圖.png

你拿手機(jī)刷著刷著使套,突然手滑點(diǎn)開(kāi)一張圖,
這圖向上無(wú)限高,向下無(wú)限深,向左無(wú)限遠(yuǎn),向右無(wú)限遠(yuǎn)鞠柄,
這圖是什么侦高?

是點(diǎn)9圖。??

大家好厌杜,我是來(lái)顛覆你對(duì)點(diǎn)9圖固有認(rèn)知的星際碼仔奉呛。

點(diǎn)9圖幾乎在每個(gè)Android工程中都或多或少地有用到,而切點(diǎn)9圖也可以說(shuō)是每個(gè)Android開(kāi)發(fā)者必備的傳統(tǒng)藝能了夯尽,但今天我們要分享的主題估計(jì)各位平時(shí)比較少接觸到瞧壮,就是——從網(wǎng)絡(luò)加載點(diǎn)9圖

為了講好這個(gè)主題呐萌,我們會(huì)從點(diǎn)9圖的基礎(chǔ)知識(shí)出發(fā)馁痴,比較網(wǎng)絡(luò)加載方式與常規(guī)用法的區(qū)別,然后分別給出一個(gè)次優(yōu)級(jí)和更優(yōu)級(jí)的解決思路肺孤,可以根據(jù)你們當(dāng)前項(xiàng)目的實(shí)際情況自由選取罗晕。

照例,先給出一張思維導(dǎo)圖赠堵,方便復(fù)習(xí):

從網(wǎng)絡(luò)加載點(diǎn)9圖.png

點(diǎn)9圖的基礎(chǔ)知識(shí)

點(diǎn)9圖小渊,官方的正式名稱(chēng)為9-patch,是一種可拉伸的位圖圖像格式茫叭,因其必須以.9.png為擴(kuò)展名進(jìn)行保存而得名酬屉,通常被用作各類(lèi)視圖控件的背景。

其典型的一個(gè)應(yīng)用就是IM中的聊天氣泡框揍愁,氣泡框的寬高會(huì)隨著我們輸入文本的長(zhǎng)短而自適應(yīng)拉伸呐萨,但氣泡框資源本身并不會(huì)因拉伸而失真。

聊天氣泡框.jpg

這么神奇的效果是怎么實(shí)現(xiàn)的呢莽囤?

答案是:四條黑線谬擦。

忽略掉.9.png的擴(kuò)展名,點(diǎn)9圖的本質(zhì)其實(shí)就是一張標(biāo)準(zhǔn)的PNG格式圖片朽缎,而與其他普通PNG格式圖片的不同之處在于惨远,點(diǎn)9圖在其圖片的四周額外包含了1像素寬的黑色邊框,用于定義圖片的可拉伸的區(qū)域與可繪制的區(qū)域话肖,以實(shí)現(xiàn)根據(jù)視圖內(nèi)容自動(dòng)調(diào)整圖片大小的效果北秽。

可拉伸區(qū)域的定義

可拉伸區(qū)域由左側(cè)及頂部一條或多條黑線來(lái)定義,左側(cè)的黑色邊框定義了縱向拉伸的區(qū)域最筒,頂部的黑色邊框定義了橫向拉伸的區(qū)域贺氓,拉伸的效果是通過(guò)復(fù)制區(qū)域內(nèi)圖片的像素來(lái)實(shí)現(xiàn)的。

可拉伸區(qū)域.png

可以看到床蜘,由于可拉伸區(qū)域選擇的都是比較平整的區(qū)域掠归,而沒(méi)有覆蓋到四周的圓角缅叠,因此圖片無(wú)論怎么縱向或橫向拉伸,四周的圓角都不會(huì)因此而變形失真虏冻。

可繪制區(qū)域的定義

可繪制區(qū)域由右側(cè)及底部的各一條黑線來(lái)定義,稱(chēng)為內(nèi)邊距線弹囚。如果沒(méi)有添加內(nèi)邊距線厨相,視圖內(nèi)容將默認(rèn)填滿(mǎn)整個(gè)視圖區(qū)域。

沒(méi)有添加內(nèi)邊距線.png

而如果添加了內(nèi)邊距線鸥鹉,則視圖內(nèi)容僅會(huì)在右側(cè)及底部的黑線所定義的區(qū)域內(nèi)顯示蛮穿,如果視圖內(nèi)容顯示不下,則圖片會(huì)拉伸至合適的尺寸毁渗。

添加了內(nèi)邊距線.png

Glide能處理點(diǎn)9圖嗎

點(diǎn)九圖的常規(guī)用法践磅,就是以.9.png為擴(kuò)展名保存在項(xiàng)目的 res/drawable/ 目錄下,并隨著項(xiàng)目一起打包到 *.apk 文件中灸异,然后跟其他普通的PNG格式圖片一樣正常使用即可府适。

但這種情況在改成了從網(wǎng)絡(luò)加載點(diǎn)9圖之后有所變化。

問(wèn)題在于肺樟,即使強(qiáng)大如Glide檐春,對(duì)于從網(wǎng)絡(luò)加載點(diǎn)9圖的這種場(chǎng)景,也沒(méi)有做很好的適配么伯,以至于我們加載完圖片之后會(huì)發(fā)現(xiàn)...

完疟暖!全!沒(méi)田柔!有俐巴!拉!伸硬爆!效欣舵!果!

焯.gif

要理解這背后的原因摆屯,我們需要把目光轉(zhuǎn)移到一個(gè)原本在打包過(guò)程中常常被我們忽視的角色——AAPT邻遏。

AAPT是什么?

AAPT即Android Asset Packaging Tool虐骑,是用于構(gòu)建*.apk文件的Android資源打包工具准验,默認(rèn)存放在Android SDK的build-tools目錄下。

盡管我們很少直接使用AAPT工具廷没,但其卻是.apk文件打包流程中不可或缺的重要一環(huán)糊饱,具體可參照下面的.apk文件詳細(xì)構(gòu)建流程圖。

*.apk文件詳細(xì)構(gòu)建流程圖.png

流程里颠黎,AAPT工具最重要的功能另锋,就是獲取并編譯我們應(yīng)用的資源文件滞项,例如AndroidManifest.xml清單文件和Activity的XML布局文件。 還有就是生成了一個(gè)R.java夭坪,以便我們從 Java 代碼中根據(jù)id索引到對(duì)應(yīng)的資源文判。

而常規(guī)用法下的點(diǎn)9圖之所以能正常工作,也離不開(kāi)打包時(shí)室梅,AAPT對(duì)于包含點(diǎn)9圖在內(nèi)的PNG格式圖片的預(yù)處理戏仓。

那么,AAPT的預(yù)處理具體都做了哪些事情呢亡鼠?

AAPT對(duì)點(diǎn)九圖做的預(yù)處理

首先赏殃,我們要了解的是,在Android的世界里间涵,存在著兩種不同形式的點(diǎn)9圖文件仁热,分別是“源類(lèi)型(source)”和“已編譯類(lèi)型(compiled)”。

源類(lèi)型就是前面所提到的勾哩,使用了包括Draw 9-patch在內(nèi)的點(diǎn)9圖制作工具所創(chuàng)建的抗蠢、四周帶有1像素寬黑色邊框的PNG圖片。

ic_bubble_right.9.png

而已編譯類(lèi)型指的是钳幅,把之前定義好的點(diǎn)九圖數(shù)據(jù)(可拉伸區(qū)域&可繪制區(qū)域等)寫(xiě)入原先格式的輔助數(shù)據(jù)塊后物蝙,把四周的黑色邊框抹除了的PNG圖片。

ic_bubble_right.png

這里稍微提一下PNG圖片的文件格式敢艰。

Png文件結(jié)構(gòu).png

在文件頭之外诬乞,PNG圖片使用了基于“塊(chunk)”的存儲(chǔ)結(jié)構(gòu),每個(gè)塊負(fù)責(zé)傳達(dá)有關(guān)圖像的某些信息钠导。

塊有關(guān)鍵塊輔助塊兩種類(lèi)型震嫉,關(guān)鍵塊包含了讀取和渲染PNG文件所需的信息,必不可少牡属。而輔助數(shù)據(jù)塊則是可選的票堵,程序在遇到它不理解的輔助塊時(shí),可以安全地忽略它逮栅,這種設(shè)計(jì)可以保持與舊版本的兼容性悴势。

點(diǎn)九圖數(shù)據(jù)所放入的,正是一個(gè)tag為“npTc”的輔助數(shù)據(jù)塊措伐。

AAPT在打包過(guò)程中對(duì)點(diǎn)9圖的預(yù)處理特纤,其實(shí)就是將點(diǎn)9圖從源類(lèi)型轉(zhuǎn)換為已編譯類(lèi)型的過(guò)程,也只有已編譯類(lèi)型的點(diǎn)9圖才能被Android系統(tǒng)識(shí)別并處理侥加,從而達(dá)到根據(jù)視圖內(nèi)容自動(dòng)調(diào)整圖片大小的效果捧存。

而直接從網(wǎng)絡(luò)加載的點(diǎn)9圖則缺少這個(gè)過(guò)程,我們實(shí)際拿到的是沒(méi)有經(jīng)過(guò)AAPT預(yù)處理的源類(lèi)型,Android系統(tǒng)就只會(huì)把它當(dāng)普通的PNG格式圖片一樣處理昔穴,因此展示時(shí)會(huì)有殘留在四周的黑色邊框镰官,并且當(dāng)視圖內(nèi)容過(guò)大時(shí),圖片就會(huì)因?yàn)椴缓侠砝於a(chǎn)生明顯的失真吗货。

四周殘留黑線.jpg

明白了這一層的原理之后泳唠,我們也就有了一個(gè)次優(yōu)級(jí)別的解決思路,也即:

用AAPT命令行還原對(duì)點(diǎn)9圖的預(yù)處理

AAPT同時(shí)也是一個(gè)命令行工具卿操,其在打包過(guò)程中參與的多項(xiàng)工作都可以通過(guò)命令行來(lái)實(shí)現(xiàn)警检。

其中就包括對(duì)PNG格式圖片的預(yù)處理。

于是害淤,具體可操作的步驟也很清晰了:

步驟1:設(shè)計(jì)組產(chǎn)出源類(lèi)型的點(diǎn)9圖后,即利用AAPT工具轉(zhuǎn)換為已編譯類(lèi)型

這樣做還有一個(gè)好處就是拓售,AAPT命令行工具會(huì)校驗(yàn)源類(lèi)型點(diǎn)9圖的規(guī)格窥摄,如果不合規(guī)就會(huì)報(bào)錯(cuò)并給出原因提示,這樣就可以在生產(chǎn)端時(shí)就保證產(chǎn)出點(diǎn)9圖的合規(guī)性础淤,而不是等到展示的時(shí)候才發(fā)現(xiàn)有問(wèn)題崭放。

命令行如下:

 aapt s[ingleCrunch] [-v] -i inputfile -o outputfile

[]表示是可選的完整命令或參數(shù)。

步驟2:交付到資源上傳平臺(tái)后鸽凶,后端改由下發(fā)這種已編譯類(lèi)型的點(diǎn)9圖

這個(gè)過(guò)程還需保證不會(huì)因流量壓縮而將圖片轉(zhuǎn)為Webp格式币砂,或者造成“npTc”的輔助數(shù)據(jù)塊丟失。

步驟3:客戶(hù)端拿到后還需一些額外的處理玻侥,以正常識(shí)別和展示點(diǎn)9圖

這里主要涉及到2個(gè)問(wèn)題:

  1. 我們?cè)趺粗老掳l(fā)的資源是已編譯類(lèi)型的點(diǎn)9圖决摧?
  2. 我們?cè)趺锤嬖V系統(tǒng)以點(diǎn)9圖的形式正確處理這張圖?

這2個(gè)問(wèn)題都可以從Android SDK源碼中找到答案凑兰。

關(guān)于問(wèn)題1掌桩,我們可以從點(diǎn)9圖的常見(jiàn)應(yīng)用場(chǎng)景,即設(shè)為視圖控件背景的API入手姑食,從View#setBackground方法一路深入直至BitmapFactory#setDensityFromOptions方法波岛,就可以看到:

    private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
            ...
            byte[] np = outputBitmap.getNinePatchChunk();
            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
           ...
    }

Bitmap#getNinePatchChunk方法返回的是一個(gè)byte數(shù)組類(lèi)型的數(shù)據(jù),從方法名就可以看出其正是關(guān)于點(diǎn)九圖規(guī)格的輔助塊數(shù)據(jù):

    public byte[] getNinePatchChunk() {
        return mNinePatchChunk;
    }

NinePatch#isNinePatchChunk方法是一個(gè)Native函數(shù)音半,我們等到后面深入點(diǎn)九圖Native層結(jié)構(gòu)體時(shí)再展開(kāi)講:

    public native static boolean isNinePatchChunk(byte[] chunk);

而關(guān)于問(wèn)題2则拷,我們可以通過(guò)查找對(duì)Bitmap#getNinePatchChunk方法的引用,在Drawable#createFromResourceStream方法中找到一個(gè)參考例子:

    public static Drawable createFromResourceStream(@Nullable Resources res,
            @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
            @Nullable BitmapFactory.Options opts) {
        ...
        Rect pad = new Rect();
        ...
        Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
        if (bm != null) {
            byte[] np = bm.getNinePatchChunk();
            if (np == null || !NinePatch.isNinePatchChunk(np)) {
                np = null;
                pad = null;
            }

            final Rect opticalInsets = new Rect();
            bm.getOpticalInsets(opticalInsets);
            return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
        }
        return null;
    }
    private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
            Rect pad, Rect layoutBounds, String srcName) {

        if (np != null) {
            return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
        }

        return new BitmapDrawable(res, bm);
    }

可以看到曹鸠,它是通過(guò)在判斷NinePatchChunk數(shù)據(jù)不為空后煌茬,構(gòu)建了一個(gè)NinePatchDrawable來(lái)告訴系統(tǒng)以點(diǎn)9圖的形式正確處理這張圖的。

于是我們可以得出結(jié)論物延,客戶(hù)端要做的額外處理宣旱,就是在拿到已編譯類(lèi)型的點(diǎn)9圖并構(gòu)建為Bitmap后:

  1. 先調(diào)用Bitmap#getNinePatchChunk方法嘗試獲取點(diǎn)9圖數(shù)據(jù)

  2. 再通過(guò)NinePatch#isNinePatchChunk方法判斷是不是點(diǎn)9圖數(shù)據(jù)。

  3. 如果是點(diǎn)9圖數(shù)據(jù)叛薯,則利用這個(gè)點(diǎn)9圖數(shù)據(jù)構(gòu)建一個(gè)NinePatchDrawable

  4. 如果不是浑吟,則構(gòu)建一個(gè)BitmapDrawable笙纤。

示例代碼如下:

        Glide.with(context).asBitmap().load(url)
            .into(object : CustomTarget<Bitmap>(){
                override fun onResourceReady(bitmap: Bitmap, transition: Transition<in Bitmap>?) {
                    try {
                        val chunk = bitmap.ninePatchChunk
                        val drawable = if (NinePatch.isNinePatchChunk(chunk)) {
                            NinePatchDrawable(context.resources, bitmap, chunk, Rect(), null)
                        } else {
                            BitmapDrawable(context.resources, bitmap);
                        }
                        view.background = drawable;
                    } catch (e: Exception) {
                        e.printStackTrace();
                    }
                }
    
                override fun onLoadCleared(placeholder: Drawable?) {
                }
    
            })

這樣就滿(mǎn)足了嗎?并沒(méi)有组力。方案本身雖然可行省容,但讓一向習(xí)慣可視化界面操作的設(shè)計(jì)組同事執(zhí)行命令行,實(shí)在是有點(diǎn)太為難他們了燎字,并且每次產(chǎn)出資源后都要用AAPT工具處理一遍腥椒,也確實(shí)有點(diǎn)麻煩。

話(huà)說(shuō)回來(lái)候衍,命令行工具的底層肯定還是依賴(lài)代碼來(lái)實(shí)現(xiàn)的笼蛛,那有沒(méi)有可能在客戶(hù)端側(cè)實(shí)現(xiàn)一套與AAPT工具一樣的邏輯呢?這就引出了我們一個(gè)更次優(yōu)級(jí)別的解決思路蛉鹿,也即:

在客戶(hù)端側(cè)還原對(duì)點(diǎn)9圖的預(yù)處理

透過(guò)上一個(gè)方案我們可以了解到滨砍,最關(guān)鍵的地方還是那個(gè)byte數(shù)組類(lèi)型的點(diǎn)九圖數(shù)據(jù)塊(NineChunk),如果我們能知道這個(gè)數(shù)據(jù)塊里面實(shí)際包含什么內(nèi)容妖异,就有機(jī)會(huì)在在客戶(hù)端側(cè)構(gòu)造出一份類(lèi)似的數(shù)據(jù)惋戏。

上一個(gè)方案中提到的NinePatch#isNinePatchChunk方法就是我們的突破點(diǎn)。

接下來(lái)他膳,就讓我們進(jìn)入Native層查看isNinePatchChunk方法的源碼實(shí)現(xiàn)吧:

    static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
        if (NULL == obj) {
            return JNI_FALSE;
        }
        if (env->GetArrayLength(obj) < (int)sizeof(Res_png_9patch)) {
            return JNI_FALSE;
        }
        const jbyte* array = env->GetByteArrayElements(obj, 0);
        if (array != NULL) {
            const Res_png_9patch* chunk = reinterpret_cast<const Res_png_9patch*>(array);
            int8_t wasDeserialized = chunk->wasDeserialized;
            env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array), JNI_ABORT);
            return (wasDeserialized != -1) ? JNI_TRUE : JNI_FALSE;
        }
        return JNI_FALSE;
    }

可以看到响逢,在isNinePatchChunk方法內(nèi)部實(shí)際是將傳入的byte數(shù)組類(lèi)型的點(diǎn)9圖數(shù)據(jù)轉(zhuǎn)為一個(gè)Res_png_9patch類(lèi)型的結(jié)構(gòu)體,再通過(guò)一個(gè)wasDeserialized的結(jié)構(gòu)變量來(lái)判斷是不是點(diǎn)9圖數(shù)據(jù)的棕孙。

這個(gè)Res_png_9patch類(lèi)型的結(jié)構(gòu)體內(nèi)部是這樣的:

 * This chunk specifies how to split an image into segments for
 * scaling.
 *
 * There are J horizontal and K vertical segments.  These segments divide
 * the image into J*K regions as follows (where J=4 and K=3):
 *
 *      F0   S0    F1     S1
 *   +-----+----+------+-------+
 * S2|  0  |  1 |  2   |   3   |
 *   +-----+----+------+-------+
 *   |     |    |      |       |
 *   |     |    |      |       |
 * F2|  4  |  5 |  6   |   7   |
 *   |     |    |      |       |
 *   |     |    |      |       |
 *   +-----+----+------+-------+
 * S3|  8  |  9 |  10  |   11  |
 *   +-----+----+------+-------+
 *
 * Each horizontal and vertical segment is considered to by either
 * stretchable (marked by the Sx labels) or fixed (marked by the Fy
 * labels), in the horizontal or vertical axis, respectively. In the
 * above example, the first is horizontal segment (F0) is fixed, the
 * next is stretchable and then they continue to alternate. Note that
 * the segment list for each axis can begin or end with a stretchable
 * or fixed segment.
 * /
struct alignas(uintptr_t) Res_png_9patch
{
    Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
                       yDivsOffset(0), colorsOffset(0) { }

    int8_t wasDeserialized;
    uint8_t numXDivs;
    uint8_t numYDivs;
    uint8_t numColors;

    // The offset (from the start of this structure) to the xDivs & yDivs
    // array for this 9patch. To get a pointer to this array, call
    // getXDivs or getYDivs. Note that the serialized form for 9patches places
    // the xDivs, yDivs and colors arrays immediately after the location
    // of the Res_png_9patch struct.
    uint32_t xDivsOffset;
    uint32_t yDivsOffset;

    int32_t paddingLeft, paddingRight;
    int32_t paddingTop, paddingBottom;

    enum {
        // The 9 patch segment is not a solid color.
        NO_COLOR = 0x00000001,

        // The 9 patch segment is completely transparent.
        TRANSPARENT_COLOR = 0x00000000
    };

    // The offset (from the start of this structure) to the colors array
    // for this 9patch.
    uint32_t colorsOffset;
    ...

    inline int32_t* getXDivs() const {
        return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + xDivsOffset);
    }
    inline int32_t* getYDivs() const {
        return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + yDivsOffset);
    }
    inline uint32_t* getColors() const {
        return reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(this) + colorsOffset);
    }
} __attribute__((packed));

很明顯舔亭,這個(gè)結(jié)構(gòu)體就是用來(lái)存儲(chǔ)點(diǎn)9圖規(guī)格數(shù)據(jù)的,我們可以根據(jù)該結(jié)構(gòu)體的源碼和注釋梳理出每個(gè)變量的含義:

每個(gè)變量的含義.png

根據(jù)該結(jié)構(gòu)體注釋中的描述散罕,這個(gè)結(jié)構(gòu)體是用于指定如何將圖像分割成多個(gè)部分以進(jìn)行縮放的分歇,其中:

  • Sx標(biāo)簽標(biāo)記的是拉伸區(qū)域(stretchable),F(xiàn)x標(biāo)簽標(biāo)記的是固定區(qū)域(fixed)
  • mDivX描述了所有S區(qū)域水平方向的起始位置和結(jié)束位置
  • mDivY描述了所有S區(qū)域垂直方向的起始位置和結(jié)束位置
  • mColor描述了每個(gè)小區(qū)域的顏色

以該結(jié)構(gòu)體注釋中的例子來(lái)說(shuō)欧漱,mDivX职抡,mDivY,mColor分別如下:

 *      F0   S0    F1     S1
 *   +-----+----+------+-------+
 * S2|  0  |  1 |  2   |   3   |
 *   +-----+----+------+-------+
 *   |     |    |      |       |
 *   |     |    |      |       |
 * F2|  4  |  5 |  6   |   7   |
 *   |     |    |      |       |
 *   |     |    |      |       |
 *   +-----+----+------+-------+
 * S3|  8  |  9 |  10  |   11  |
 *   +-----+----+------+-------+
mDivX = [ S0.start, S0.end, S1.start, S1.end];
mDivY = [ S2.start, S2.end, S3.start, S3.end];
mColor = [c[0],c[1],...,c[11]]

我畫(huà)了一張示意圖误甚,應(yīng)該會(huì)更方便理解一點(diǎn):

注釋例子示意圖.png

這幾個(gè)結(jié)構(gòu)體變量所描述的缚甩,不正是我們?cè)搭?lèi)型的點(diǎn)9圖四周所對(duì)應(yīng)的那些黑色邊框的位置嗎?

那么窑邦,現(xiàn)在我們只需要在Java層定義一個(gè)與Res_png_9patch結(jié)構(gòu)體的數(shù)據(jù)結(jié)構(gòu)一模一樣的類(lèi)擅威,并在填充關(guān)鍵的變量數(shù)據(jù)后序列化為byte數(shù)組類(lèi)型的數(shù)據(jù),就可以作為NinePatchDrawable構(gòu)造函數(shù)的參數(shù)了冈钦。

怎么做呢郊丛?這部分有點(diǎn)復(fù)雜,Github上已經(jīng)有一個(gè)大神開(kāi)源出了方案,可以參考下其源碼實(shí)現(xiàn):https://github.com/Anatolii/NinePatchChunk

這里只給出使用層的示例代碼:

     Glide.with(context).asBitmap().load(url)
            .into(object : CustomTarget<Bitmap>(){
                override fun onResourceReady(bitmap: Bitmap, transition: Transition<in Bitmap>?) {
                    try {
                        val drawable = NinePatchChunk.create9PatchDrawable(textBackground.context, resource, null)
                        view.background = drawable;
                    } catch (e: Exception) {
                        e.printStackTrace();
                    }
                }

                override fun onLoadCleared(placeholder: Drawable?) {
                }

            })

NinePatchChunk類(lèi)即為前面說(shuō)的在Java層定義的類(lèi)厉熟,并提供了幾個(gè)靜態(tài)方法用于創(chuàng)建NinePatchDrawable导盅,其在內(nèi)部會(huì)去檢測(cè)傳入的Bitmap實(shí)例屬于哪種類(lèi)型:

    public static BitmapType determineBitmapType(Bitmap bitmap) {
        if (bitmap == null) return NULL;
        byte[] ninePatchChunk = bitmap.getNinePatchChunk();
        if (ninePatchChunk != null && android.graphics.NinePatch.isNinePatchChunk(ninePatchChunk))
            return NinePatch;
        if (NinePatchChunk.isRawNinePatchBitmap(bitmap))
            return RawNinePatch;
        return PlainImage;
    }

NinePatch即為已編譯類(lèi)型的點(diǎn)9圖,RawNinePatch即為源類(lèi)型的點(diǎn)9圖揍瑟,源類(lèi)型是通過(guò)PNG圖片4個(gè)角像素是否為透明且是否包含黑色邊框判斷的白翻。

    public static boolean isRawNinePatchBitmap(Bitmap bitmap) {
        if (bitmap == null) return false;
        if (bitmap.getWidth() < 3 || bitmap.getHeight() < 3)
            return false;
        if (!isCornerPixelsAreTrasperent(bitmap))
            return false;
        if (!hasNinePatchBorder(bitmap))
            return false;
        return true;
    }

好了,這個(gè)就是今天要分享的內(nèi)容绢片。最后留給大家一個(gè)問(wèn)題滤馍,你覺(jué)得.9.png的擴(kuò)展名對(duì)于從網(wǎng)絡(luò)加載點(diǎn)九圖有影響嗎?

少俠底循,請(qǐng)留步巢株!若本文對(duì)你有所幫助或啟發(fā),還請(qǐng):

  1. 點(diǎn)贊熙涤,讓更多的人能看到纯续!
  2. 收藏?,好文值得反復(fù)品味灭袁!
  3. 關(guān)注?,不錯(cuò)過(guò)每一次更文窗看!

你的支持是我繼續(xù)創(chuàng)作的動(dòng)力茸歧,感謝!

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末显沈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拉讯,更是在濱河造成了極大的恐慌,老刑警劉巖魔慷,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異院尔,居然都是意外死亡蜻展,警方通過(guò)查閱死者的電腦和手機(jī)邀摆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)栋盹,“玉大人施逾,你說(shuō)我怎么就攤上這事。” “怎么了汉额?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)整葡。 經(jīng)常有香客問(wèn)我讥脐,道長(zhǎng)遭居,這世上最難降的妖魔是什么旬渠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮枪蘑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岳颇。我一直安慰自己颅湘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布闯参。 她就那樣靜靜地躺著,像睡著了一般新博。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赫悄,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天玩讳,我揣著相機(jī)與錄音,去河邊找鬼熏纯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛误窖,可吹牛的內(nèi)容都是我干的叮盘。 我是一名探鬼主播霹俺,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愈魏!你這毒婦竟也來(lái)了想际?” 一聲冷哼從身側(cè)響起培漏,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牌柄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后珊佣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體披粟,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年守屉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡须板,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出习瑰,到底是詐尸還是另有隱情,我是刑警寧澤柠横,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布课兄,位于F島的核電站,受9級(jí)特大地震影響烟阐,放射性物質(zhì)發(fā)生泄漏紊扬。R本人自食惡果不足惜唉擂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玩祟。 院中可真熱鬧,春花似錦空扎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宴抚。三九已至甫煞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抚吠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工喊式, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萧朝,地道東北人岔留。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓检柬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親里逆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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