當(dāng)你的設(shè)計(jì)師要求你在某個(gè) View 上增加陰影效果扔枫,那你只需要認(rèn)真閱讀本文,陰影的問題就不再是問題锹安。
一短荐、前言
設(shè)計(jì)師的世界,與常人不同叹哭,有時(shí)候想要扁平化的風(fēng)格搓侄,有時(shí)候又想要擬物化的風(fēng)格。而在 Material Design 出來之后话速,為 UI 元素引入了高度的概念讶踪,它可以讓某個(gè)元素更為突出,顯示出它的重要性泊交,更讓人有點(diǎn)擊的欲望乳讥。
在擬物化的設(shè)計(jì)里,UI 元素的高度廓俭,反應(yīng)在效果上云石,就是在邊框上有陰影的效果,感覺它是距離底部有一個(gè)層次的關(guān)系研乒。在 Material Design 的設(shè)計(jì)中汹忠,也大量的使用了 陰影 的效果,例如:FloatingActionButton雹熬、CardView 這些控件宽菜,都是默認(rèn)支持陰影效果的。
如果你想了解 Material Design 中竿报,更多關(guān)于陰影的設(shè)計(jì)铅乡,可以查閱官方文檔。
https://material.io/guidelines/material-design/elevation-shadows.html?hl=zh-cn
接下來烈菌,我們就來介紹一下阵幸,在 Android 的不同版本中,使用不同的方式芽世,去實(shí)現(xiàn)陰影的效果挚赊。
先來看看實(shí)現(xiàn)的效果,雖然多济瓢,但是它們實(shí)現(xiàn)的方法都不相同荠割。
二、陰影的效果
在擬物化的世界里葬荷,陰影主要是對(duì)三維空間中的 Z 屬性進(jìn)行操作涨共。下面是官網(wǎng)的介紹。
由 Z 屬性所表示的視圖高度將決定其陰影的視覺外觀:擁有較高 Z 值的視圖將投射更大且更柔和的陰影宠漩。 擁有較高 Z 值的視圖將擋住擁有較低 Z 值的視圖举反;不過視圖的 Z 值并不影響視圖的大小。
陰影是由提升的視圖的父項(xiàng)所繪制扒吁,因此將受到標(biāo)準(zhǔn)視圖裁剪的影響火鼻,而在默認(rèn)情況下裁剪將由父項(xiàng)執(zhí)行。
https://developer.android.com/training/material/shadows-clipping.html?hl=zh-cn#Elevation
靜態(tài)效果如下:
再加上雕崩,動(dòng)態(tài)的效果應(yīng)該更能讓你對(duì)陰影有所理解魁索。
三、使用標(biāo)準(zhǔn) Api
Material Design 首次出現(xiàn)在 Android 5.0 中盼铁,之后又有一些 Support 包粗蔚,讓更低的版本,對(duì) Material Design 進(jìn)行支持饶火。
而在 Api Level 21 之中鹏控,增加了兩個(gè)屬性 :
- elevation:高度,用于提升 UI 元素高度的屬性肤寝。
- translationZ:Z 軸的變換效果当辐。
這兩個(gè)屬性,有對(duì)應(yīng)的 xml 屬性和 setXxx()
方法鲤看,而 Z 軸的改變缘揪,主要是由這兩個(gè)屬性決定的。
Z = elevation + translationZ
所以义桂,如果你的 App 的 minSdkVersion 就是 21 的話找筝,直接使用這兩個(gè)屬性是最優(yōu)的解決辦法。
3.1 elevation 屬性
elevation 屬性慷吊,主要用于給 View 增加一個(gè)高度呻征,可以直接被加在 View 控件上,呈現(xiàn)在界面上罢浇,就是一個(gè)帶陰影的效果陆赋。
在 layout-xml 布局中,可以通過 android:elevation
屬性來設(shè)置嚷闭,而在 Java 代碼中攒岛,通過 View.setElevation()
方法來使用它。
直接使用 elevation 屬性設(shè)置即可胞锰,它接收一個(gè)高度的參數(shù)灾锯,只需要按我們的需要配置即可。
需要注意的是嗅榕,View 的陰影一定是需要有背景的 View 在視覺上增高之后顺饮,投射出來的吵聪。也就是類似于打光的陰影效果。簡單來說兼雄,就是需要為 View 設(shè)置一個(gè) Background吟逝,可以使用 android:background
屬性或者 View.setBackground()
方法設(shè)置,否者 elevation 的屬性設(shè)置將無效赦肋。這里的 Background 只需要設(shè)置一個(gè) Drawable 即可块攒,你當(dāng)然也可以選擇一個(gè)圖片或者一個(gè)純色的 <shape/> 了。
下面來看看 elevation 屬性的效果:
往深里再看看 elevation 屬性的實(shí)現(xiàn)方式佃乘。
它最終還是調(diào)用的 mRenderNode 去做的操作囱井,在追蹤下去,就會(huì)發(fā)現(xiàn)它底層是用的 native 的方法實(shí)現(xiàn)的趣避,所以應(yīng)該不是我們所理解的用 2D 的漸變模擬陰影的效果庞呕。
3.2 translationZ 屬性
translationZ 屬性,主要用于給 View 增加一個(gè)在 Z 軸上的變換效果程帕。它和 elevation 配合起來千扶,就是一個(gè)一加一等于二的效果。也可以用于設(shè)置 View 的高度骆捧。
在 layout-xml 布局中澎羞,可以通過 android:translationZ
屬性來設(shè)置它,而在 Java 代碼中敛苇,可以通過 View.setTranslationZ()
方法來使用它妆绞。
一般來說,我們可以直接使用 android:translationZ
屬性來設(shè)置 View枫攀,當(dāng)你配合 android:elevation
屬性一起使用的時(shí)候括饶,它們對(duì) View 的高度是累加的,當(dāng)然你也可以只使用其中一個(gè)屬性来涨。
而看到 translationZ 這樣的屬性图焰,很輕易就聯(lián)想到了 translationX 和 translationY 了,它們實(shí)際上就是不同維度的設(shè)置蹦掐,思路上很像技羔,但是原理不同。對(duì) X卧抗、Y 軸的操作并沒有 Api Level 的限制藤滥,這一點(diǎn)需要清楚。
和 elevation 屬性一樣社裆,translationZ 也是需要配合 Background 的設(shè)置才會(huì)生效的拙绊,這個(gè)應(yīng)該不難理解。
下面我們來看看 translationZ 屬性的設(shè)置效果:
使用 translationZ 屬性實(shí)現(xiàn)的效果,看著和 elevation 的效果很像标沪,而它內(nèi)部也是依賴于 mRenderNode 去做的實(shí)現(xiàn)榄攀。
3.3 ViewCompat 來兼容 Api
前面就已經(jīng)提到,當(dāng)你的 minSdkVersion 達(dá)不到 elevation 和 translationZ 這兩個(gè) Api 的要求金句,設(shè)置為 Api Level 21(Android 5.0) 以下檩赢。你在使用這兩個(gè)屬性的時(shí)候,會(huì)給你提示 Warning趴梢,如果打包的時(shí)候有 Lint 的校驗(yàn),也是會(huì)提示并且導(dǎo)致打包失敗的币他。
不過看提示你也能發(fā)現(xiàn)到底是什么問題:
Attribute elevation is only used in API level 21 and higher
如果已經(jīng)明確在低于 Api Level 21 之下的版本坞靶,都不加陰影的效果,你可以在布局中蝴悉,使用 tools:targetApi="lollipop"
來消除這個(gè) Warning彰阴。
如果你是在 Java 代碼中,為 View 動(dòng)態(tài)設(shè)置 elevation 或者 translationZ 屬性的話拍冠,除了使用 Build.VERSION_CODES.LOLLIPOP
判斷之外尿这,還可以使用 ViewCompat 這個(gè) Android 為我們提供的標(biāo)準(zhǔn)的 View 兼容類,當(dāng)然庆杜,這里推薦使用 ViewCompat射众。
既然要用到 ViewCompat 的話,那我們來看看它的原理是什么。
在 ViewCompat 中,會(huì)有很多個(gè)實(shí)現(xiàn)了 ViewCompatBaseImpl 的接口類裁僧,它們分別對(duì)應(yīng)了不同的 Api Level 逼泣,會(huì)在靜態(tài)代碼塊中,根據(jù)當(dāng)前運(yùn)行設(shè)備的 Api Level 碌宴,做不同的實(shí)現(xiàn)。而這些,都是高版本繼承低版本的實(shí)現(xiàn)伙菜,來達(dá)到繼承兼容的效果。
ViewCompatBaseImpl 這個(gè)接口中命迈,定義了很多關(guān)于 View 的操作 Api 贩绕,這些 Api 都是存在不同的 Api 版本限制的。
在 Api Level 21 中壶愤,本身就已經(jīng)支持了這兩個(gè)屬性丧叽,也就不存在兼容性的問題了,所以它其中會(huì)直接調(diào)用 setElevation()
和 setTranslationZ()
方法公你。
那么踊淳,我們只需要關(guān)心 Api Level 21 以下的實(shí)現(xiàn)。通常來說,我們做兼容處理迂尝,一個(gè)方案就是在低版本上脱茉,使用一些只在低版本上存在 Api,來對(duì)高版本的效果進(jìn)行模擬垄开;另外一個(gè)方案就是放棄低版本琴许,完全對(duì)它不做任何處理。
我們來看看 ViewCompat 是對(duì) Elevation 是選用的那個(gè)方案溉躲。其實(shí) Api Level 21 之下榜田,都沒有對(duì)這兩個(gè)屬性的操作方法,做任何的處理锻梳,你一路追蹤下去可以追蹤到 ViewCompatBaseImpl 箭券。
從這里可以看出,ViewCompat 沒有對(duì)這兩個(gè)方法做任何的兼容疑枯,在低版本上辩块,沒有做任何的操作,這也導(dǎo)致了你如果使用 ViewCompat 的話荆永,在低版本上是不會(huì)有陰影的效果的废亭。沒有就是沒有,這里就不再單獨(dú)展示了具钥。
那看看使用 ViewCompat 在高版本上的效果圖豆村,其實(shí)和之前的也沒啥區(qū)別,不過擺在一起看更清晰一些骂删。
3.4 標(biāo)準(zhǔn) Api 小結(jié)
到現(xiàn)在你也能看到你画,如果不在意 Api level 的話,你完全可以使用 android:elevation
和 android:translationZ
兩個(gè)屬性來做的陰影的效果桃漾,效果也是非常好的坏匪,而且它的陰影實(shí)際上是不占用 View 的布局大小的,它會(huì)在原本的布局之外撬统,向外擴(kuò)散适滓,所以也不會(huì)影響 View 本身大小的視覺效果。
不過它也有缺陷恋追,你只能通過設(shè)定這兩個(gè)屬性來調(diào)整陰影的大小凭迹,沒辦法做到精確掌控,并且無法修改陰影的顏色苦囱。
最新的 Android 版本市場占有率嗅绸,你可以在這個(gè)網(wǎng)站上查到。
https://developer.android.com/about/dashboards/index.html?hl=zh-cn
截止到本文編寫的時(shí)候撕彤,低于 5.0 的版本鱼鸠,差不多在 20% 左右猛拴,是否對(duì)這部分用戶,放棄陰影的效果蚀狰,取決于你的產(chǎn)品和設(shè)計(jì)師愉昆。
如果你需要兼容低版本的設(shè)備,后面介紹的一些方法麻蹋,都可以做到跛溉,繼續(xù)往下閱讀吧。
四扮授、使用9Patch圖
4.1 什么上 9Patch 圖
如果你需要兼容低版本的 Android 設(shè)備芳室,使用 android:elevation 和 android:translationZ 是無法做到的,它們會(huì)在低版本上失效刹勃,完全沒有效果堪侯,當(dāng)然前提是你需要做好 Warning 的處理。
而這種陰影的效果深夯,使用 .9
圖抖格,也是一個(gè)不錯(cuò)的選擇诺苹。
.9
圖 就是 9Patch咕晋, 引用官網(wǎng)的介紹:
Draw 9-patch 工具是 Android Studio 中包含的一種 WYSIWYG(所見即所得)編輯器,利用此工具收奔,您可以創(chuàng)建能夠自動(dòng)調(diào)整大小以適應(yīng)視圖內(nèi)容和屏幕尺寸的位圖圖像掌呜。圖像的選定部分可以根據(jù)圖像內(nèi)繪制的指示器在水平或豎直方向上調(diào)整比例。
https://developer.android.com/studio/write/draw9patch.html?hl=zh-cn
4.2 使用 9Patch 設(shè)置陰影
直接制作一個(gè)帶陰影效果的 .9
圖片坪哄,然后設(shè)置好內(nèi)容區(qū)域和拉伸區(qū)域质蕉,就可以在其中模擬出陰影的效果。
舉個(gè)例子翩肌,使用一個(gè) .9
圖模暗,然后設(shè)置在 ImageView 上的背景。
在 layout-xml 上念祭,只需要給 ImageView 設(shè)置好 android:background
就可以了兑宇。
來看看它實(shí)現(xiàn)的效果:
使用 .9
圖設(shè)置的陰影,效果一般都是有保障的粱坤。不過它會(huì)作為 View 的背景被設(shè)置隶糕,所以陰影上占據(jù) View 的大小的,所以使用圖片模擬出來的陰影站玄,View 本身的視覺效果會(huì)小枚驻。
放張單圖,可能看不出效果株旷,將一個(gè)使用 ViewCompat 實(shí)現(xiàn)的效果再登,放在一起,你就可以看到對(duì)比的效果。
這里霎冯,兩個(gè) ImageView 铃拇,實(shí)際設(shè)置的大小,都是 100dp沈撞,但是視覺上慷荔,使用 .9 實(shí)現(xiàn)的效果,視覺效果就會(huì)小缠俺。
4.3 快速制作 9Patch
.9
的圖显晶,一般都是設(shè)計(jì)師會(huì)提供給我們。這里也推薦一個(gè)可以制作陰影效果的在線工具壹士。
通過這個(gè)工具磷雇,你可以對(duì) .9
圖做各種調(diào)整,例如:圓角躏救、陰影的大小唯笙、陰影的顏色等等,都是非常方便的設(shè)置盒使。前面例子中使用的 .9
文件崩掘,就是使用此工具制作的。
還有一種方式少办,就是使用 <layer-list> 這個(gè)層級(jí)的 Drawable 去模擬陰影苞慢,等于一層一層的疊加。不過使用這種方式太麻煩了英妓,而且效果也很難做到非常的好挽放,一般也不推薦。
4.4 9Patch 模擬陰影小結(jié)
使用 .9 圖蔓纠,制作陰影辑畦,基本上不需要擔(dān)心效果的問題,使用起來也非常的方便腿倚。唯一的問題就是它的陰影部分纯出,會(huì)占用 View 本身的大小,導(dǎo)致 View 在視覺上縮小猴誊。
總結(jié)來說潦刃,它的優(yōu)點(diǎn):
- 實(shí)現(xiàn)方便,只需要設(shè)置背景即可懈叹。
- 陰影的效果可控乖杠,顏色、圓角澄成、陰影大小都是可以調(diào)整的胧洒。
它的缺點(diǎn)也非常的明顯:
- 為了讓 View 在視覺上和效果圖匹配畏吓,需要預(yù)留出陰影的空間。
五卫漫、使用 FAB 的原理模擬陰影
我們知道菲饼,在 Android 對(duì) Material Design 的效果中,有一些控件列赎,就是自帶陰影效果的宏悦,并且它也是對(duì)低版本兼容的。例如:FloatingActionButton 包吝、CardView 等饼煞。
那么,本小結(jié)就來看看 FloatingActionButton 實(shí)現(xiàn)陰影的原理诗越。
5.1 FAB 的陰影原理
就 FAB 這種有 Support.design 包支持的控件砖瞧,一般都有對(duì) 不同的 Api Level 做支持處理,在 FAB 之中也是一樣的嚷狞,它會(huì)根據(jù)不同的 Api Level 實(shí)現(xiàn)不同的邏輯块促。
可以看到,這里會(huì)根據(jù) 21床未、14竭翠、<14 三個(gè)條件,分別使用不同的實(shí)現(xiàn)類即硼,它們內(nèi)部實(shí)際上實(shí)現(xiàn)的都是相同的功能逃片。
如果仔細(xì)觀察這些 FAB 不同版本的實(shí)現(xiàn)類的源碼屡拨,你可以發(fā)現(xiàn)它的陰影效果只酥,都是基于一個(gè) ShadowDrawableWrapper 這個(gè) Drawable 來實(shí)現(xiàn)的。
例如在 FloatingActionButtonGingerbread 中呀狼,就有這樣一段設(shè)置背景的代碼裂允。
這里完全上依賴 ShadowDrawableWrapper 來做的陰影效果。
不過 ShadowDrawableWrapper 被聲明的可見性為包內(nèi)可見哥艇,所以我們沒有辦法直接使用它绝编。
不過,鑒于 support.design 包中的類貌踏,一般都是為了兼容做處理十饥,這里我們只需要將它和它實(shí)現(xiàn)的接口 DrawableWrapper 這兩個(gè)類,拷貝出來祖乳,就可以直接使用了逗堵。它們的源碼都在 android.support.design/widget
包下面,非常容易找到眷昆。
它的原理是在你本文需要設(shè)置的 Drawable 之外蜒秤,再包裝一個(gè) Drawable 汁咏,然后在這個(gè)包裝的 Drawable 上繪制陰影。
繪制的代碼挺多的作媚,這里就不貼代碼了攘滩,有興趣可以看看它的源碼,主要關(guān)注 drawShadow()
方法即可纸泡。
而如果你在拷貝源碼的時(shí)候漂问,應(yīng)該能發(fā)現(xiàn),它實(shí)際上是可以支持改變陰影的顏色的女揭,如果你有這種需求级解,只需要再擴(kuò)展它的構(gòu)造方法,或者直接在 colors.xml 中配置對(duì)應(yīng)的顏色田绑,它設(shè)置顏色地方如下勤哗。
可以看到,它主要用三個(gè)顏色來做一個(gè)漸變的陰影效果掩驱。
5.2 使用 FAB 的原理模擬陰影效果
前面說的芒划,我們只需要將 ShadowDrawableWrapper 和 DrawableWrapper 這兩個(gè)文件復(fù)制到我們的工程內(nèi),稍微修改一下它們的依賴關(guān)系欧穴。
如果直接拷貝源碼民逼,你會(huì)發(fā)現(xiàn)它還依賴三個(gè)顏色,分別是用于設(shè)置陰影的顏色的涮帘,這個(gè)前面也提到過拼苍。一般而言,我們不需要設(shè)置它调缨,直接從源碼中將它們拷貝出來就可以了疮鲫。
然后我們就可以在 Java 代碼中,為 View 動(dòng)態(tài)設(shè)置一個(gè)陰影效果弦叶。
這些參數(shù)俊犯,你可以自行根據(jù)效果配置,它們的含義伤哺,其實(shí)看看方法的簽名燕侠,你就清楚了,這里就不再贅述了立莉。
那么绢彤,我們來看看使用 FAB 的 ShadowDrawableWrapper 模擬出來的陰影效果如何。
5.3 FAB 模擬陰影效果小結(jié)
前面提到蜓耻,ShadowDrawableWrapper 的原理是對(duì)原本的 Drawable 做一個(gè)包裝茫舶,在外圍繪制陰影的效果,所以說它實(shí)際上媒熊,陰影部分也是需要占據(jù) View 的空間的奇适,依然會(huì)有視覺上坟比,View 會(huì)變小。
不過它的陰影顏色上可控的嚷往,也就是說我們可以動(dòng)態(tài)的為其設(shè)置陰影的顏色葛账,這樣應(yīng)該會(huì)更靈活一些。
六皮仁、模擬 CardView 實(shí)現(xiàn)的陰影
我們知道籍琳,在 Android 對(duì) Material Design 的效果中,有一些控件贷祈,就是自帶陰影效果的趋急,并且它也是對(duì)低版本兼容的。例如:FloatingActionButton 势誊、CardView 等呜达。
那么,本小結(jié)就來看看 CardView 實(shí)現(xiàn)陰影的原理粟耻。
6.1 CardView 的陰影原理
CardView 在 support.design 包中查近,你是找不到的,它被放在了 cardview-v7 包中挤忙,現(xiàn)在已經(jīng)可以單獨(dú)引用了霜威。
CardView-v7 包中,代碼非常的少册烈。
一共就這么幾個(gè)戈泼,一樣就可以看到來,有一些類是做 Api 版本兼容的赏僧,并且也上如此大猛。
在其中,還有一個(gè) RoundRectDrawableWithShadow 類次哈,它就是我們要找到胎署,CardView 實(shí)現(xiàn)的 Drawable吆录,它只在 CardViewJellybeanMr1 和 cardViewGingerbread 這兩個(gè)類中使用窑滞,CardViewApi21 中,依然是使用的 setElevation()
方法來處理的陰影恢筝。
用之前 FAB 的經(jīng)驗(yàn)哀卫,將 RoundRectDrawableWithShadow 直接拷貝出來,然后運(yùn)行你會(huì)發(fā)現(xiàn)有報(bào)錯(cuò)撬槽。主要是因?yàn)槠渲杏袀€(gè)靜態(tài)的變量 sRoundRectHelper 為空了此改,沒有被初始化。
仔細(xì)查源碼你會(huì)發(fā)現(xiàn)侄柔,它在 CardViewJellybeanMr1 和 CardViewGingerbread 的實(shí)現(xiàn)原理并不相同共啃。它們會(huì)在 initStatic()
方法中占调,對(duì) sRoundRectHelper 變量進(jìn)行初始化。
CardViewJellybeanMr1.initStatic() 方法如下:
CardViewGingerbread.initStatic() 方法如下:
可以看到它們的實(shí)現(xiàn)方法移剪,差異還是挺大的究珊。
了解清楚這些,我們只需要 RoundRectDrawableWithShadow 的構(gòu)造方法中纵苛,根據(jù) Api Level 對(duì)他們進(jìn)行不同的初始化即可剿涮,這些代碼也上拷貝出來就可以直接用的。
繪制陰影的部分都大同小異攻人,這里就不詳細(xì)看了取试,有興趣的可以執(zhí)行查看源碼幌甘,主要關(guān)注 drawShadow() 方法即可赃磨。
6.2 舉個(gè) CardView 陰影的例子
首先,將 ShadowDrawableWrapper 完整的拷貝到我們的工程里机打,并且在構(gòu)造方法中蓬坡,根據(jù) Api Level 瑟蜈,用不同的邏輯初始化 sRoundRectHelper 。
還需要將 ShadowDrawableWrapper 使用到的幾個(gè)默認(rèn)參數(shù)值也拷貝出來渣窜,當(dāng)然我們已經(jīng)有源碼了铺根,直接寫死也可以,我這里選擇將它們?cè)瓨涌截惓鰜怼?/p>
然后我們就可以在代碼中乔宿,使用這個(gè) RoundRectDrawableWithShadow 了位迂。
最終,看看實(shí)現(xiàn)的陰影效果:
6.3 CardView 模擬陰影小結(jié)
CardView 模擬的陰影效果详瑞,在低版本上掂林,也上會(huì)占用 View 的原本的大小來繪制陰影,所以視覺上也會(huì)偏小坝橡。不過在高版本上泻帮,依然上使用 elevation來實(shí)現(xiàn)的,也就會(huì)造成在不同 Api Level 下计寇,顯示的效果不一致的問題锣杂。
七、使用開源庫 ShadowLayout
最后再介紹一個(gè)開源庫番宁,用一個(gè) LayoutView 來實(shí)現(xiàn)陰影的效果元莫。
Github 地址:
它完整的庫也只有一個(gè)類加一些屬性,整個(gè)項(xiàng)目結(jié)構(gòu)如下蝶押。
并且提供了幾個(gè)屬性踱蠢,用于配置陰影的效果。
使用起來也非常的方便棋电,它上直接繼承自 FrameLayout 的茎截,所以需要作為一個(gè)布局來使用苇侵。
最后看看實(shí)現(xiàn)的效果。
它基本上可以實(shí)現(xiàn)一個(gè)類陰影的效果企锌,不過應(yīng)該是算法的問題衅檀,導(dǎo)致陰影的邊緣太齊了,看著不真實(shí)霎俩,一般不推薦使用哀军。
八、結(jié)語
介紹了這么多在 Android 下實(shí)現(xiàn)陰影的效果打却,接下來給一張完整的效果圖吧杉适,如果本文都看完了,我想你應(yīng)該知道自己應(yīng)該選擇那種方案了柳击。
今天在承香墨影公眾號(hào)的后臺(tái)猿推,回復(fù)『成長』。我會(huì)送你一些我整理的學(xué)習(xí)資料捌肴,包含:Android反編譯蹬叭、算法、設(shè)計(jì)模式状知、Web項(xiàng)目源碼秽五。
推薦閱讀:
- Glide 這樣用,更省內(nèi)存<病L勾!
- 跳槽前西设,不思考這些等于白跳0晗场!贷揽!
- 哪些年熬過的夜棠笑,終將會(huì)"回報(bào)"你的
- 當(dāng)你只有一個(gè)設(shè)備的時(shí)候,如何做好屏幕適配
- Android 源碼禽绪,在線閱讀更方便1途取!丐一!
點(diǎn)贊或者分享吧~