你拿手機(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í):
點(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ì)因拉伸而失真。
這么神奇的效果是怎么實(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ū)域選擇的都是比較平整的區(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ū)域。
而如果添加了內(nèi)邊距線鸥鹉,則視圖內(nèi)容僅會(huì)在右側(cè)及底部的黑線所定義的區(qū)域內(nèi)顯示蛮穿,如果視圖內(nèi)容顯示不下,則圖片會(huì)拉伸至合適的尺寸毁渗。
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)田柔!有俐巴!拉!伸硬爆!效欣舵!果!
要理解這背后的原因摆屯,我們需要把目光轉(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)建流程圖。
流程里颠黎,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圖片。
而已編譯類(lèi)型指的是钳幅,把之前定義好的點(diǎn)九圖數(shù)據(jù)(可拉伸區(qū)域&可繪制區(qū)域等)寫(xiě)入原先格式的輔助數(shù)據(jù)塊后物蝙,把四周的黑色邊框抹除了的PNG圖片。
這里稍微提一下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)生明顯的失真吗货。
明白了這一層的原理之后泳唠,我們也就有了一個(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)題:
- 我們?cè)趺粗老掳l(fā)的資源是已編譯類(lèi)型的點(diǎn)9圖决摧?
- 我們?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后:
先調(diào)用Bitmap#getNinePatchChunk方法嘗試獲取點(diǎn)9圖數(shù)據(jù)
再通過(guò)NinePatch#isNinePatchChunk方法判斷是不是點(diǎn)9圖數(shù)據(jù)。
如果是點(diǎn)9圖數(shù)據(jù)叛薯,則利用這個(gè)點(diǎn)9圖數(shù)據(jù)構(gòu)建一個(gè)NinePatchDrawable
如果不是浑吟,則構(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è)變量的含義:
根據(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):
這幾個(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):
- 點(diǎn)贊熙涤,讓更多的人能看到纯续!
- 收藏?,好文值得反復(fù)品味灭袁!
- 關(guān)注?,不錯(cuò)過(guò)每一次更文窗看!
你的支持是我繼續(xù)創(chuàng)作的動(dòng)力茸歧,感謝!