? ? ? ? ?滑動菜單可以說是Material Desgin中最常見的效果之一了,在許多著名的應(yīng)用中,都有滑動菜單的功能,雖然這個功能看上去好像挺復(fù)雜的,不過借助谷歌提供的各種工具,我們可以很輕松地實現(xiàn)炫酷的滑動效果,那么我們馬上開始了。
不過如果我們?nèi)孔约黑厔菥€上述功能的話,難度恐怕就很大了。幸運的是谷歌提供了一個DrawerLayout控件,借助這個控件,實現(xiàn)滑動菜單簡單又方便。
先來介紹一下DrawerLayout的用法吧。首先它是一個布局,在布局中允許放入兩個直接子控件,第一個控件是主屏幕中顯示的內(nèi)容,第二個子控件是滑動菜單中顯示的內(nèi)容。因此,我們就可以對activity_main.xml中的代碼做如下修改:?


可以看到,這里最外層的控件使用了DrawerLayout,這個控件是由support-V4庫提供的。DrawerLayout中放置了兩個直接子控件,第一個字控件是FrameLayout,用于作為主屏幕中顯示的內(nèi)容,當然里面還有我們剛剛定義的Toolbar。第二個子控件這里使用了一個TextView,用于作為滑動菜單中顯示的內(nèi)容,其實使用什么都可以,DrawerLayout并沒有限制只能使用固定的控件。
但是關(guān)于第二個控件有一點需要注意,layout_gravity這個屬性是必須指定的,因為我們需要告訴DrawerLayout滑動菜單是在屏幕的左邊還是右邊,指定left表示滑動菜單在左邊,指定right表示滑動菜單在右邊,比如英語、漢語,滑動菜單就在左邊,如果系統(tǒng)語言是從右往左的,比如阿拉伯,滑動菜單就在右邊。
沒錯,只需要改動這么多就可以了,現(xiàn)在重新運行一下程序,然后在屏幕的左側(cè)邊緣向右拖動,就可以讓滑動菜單顯示出來了,如圖12.5所示:
然后向左滑動菜單,或者點擊一下菜單以外的區(qū)域,都可以讓滑動菜單關(guān)閉掉,從而回到主界面。無論是展示還是隱藏滑動菜單,都是非常流暢的動畫度過的。
可以看到,我們只是稍微的改動了一下布局文件,就能實現(xiàn)如此炫酷的效果,是不是覺得挺激動的啦?而很多用戶根本就不知道有這個功能,那么該怎么提示他們啦?
Material Desgin建議的做法實在Toolbar的最左邊加入一個導航按鈕,點擊了按鈕也會將滑動菜單的內(nèi)容展示出來。這樣就相當于給用戶提供了兩種打開滑動菜單的的方式,防止一些用戶不知道屏幕的左側(cè)邊緣是可以拖動的。
下面我們開始來實現(xiàn)這個功能。首先我準備了一張導航按鈕圖標ic_menu.png,將它放在了drawable-xxhdpi目錄下。然后修改MainActivity中的代碼,如下所示:
這里我們并沒有改動多少代碼findViewById()方法得到了DrawerLayout的實例,然后調(diào)用getSupportActionBar()方法得到了ActionBar的實例,雖然這個ActionBar的具體實現(xiàn)是由ToolBar來完成的。接著調(diào)用ActionBar的setDisplayHomeAsUpEnabled()方法讓導航按鈕顯示出來,有調(diào)用了setHomeAsUpIndicator()方法來設(shè)置一個導航按鈕圖標。實際上Toolbar最左側(cè)的這個按鈕就叫做HomeAsUp按鈕,他默認的圖標是一個返回的箭頭,含義是返回上一個活動。很明顯這里我們將它默認的樣式和作用進行了修改。
接下來早onOptionsItemSelected()方法中對HomeAsUp按鈕的點擊事件進行處理,HomeAsUp按鈕的id永遠都是android.id.home。然后調(diào)用DrawerLayout的openDrawer()方法將滑動菜單展示出來,注意openDrawer()方法要求傳入Gravity參數(shù),為了保證這里的行為和XML中定義的一致,我們傳入了GravityCompat.start?,F(xiàn)在重新運行一下程序,效果如圖12.6所示。
可以看到,在Toolbar的最左邊出現(xiàn)了一個導航按鈕,用戶看到這個按鈕就知道這坑定是可以點擊的?,F(xiàn)在點擊一下這個按鈕,滑動菜單界面就會再次展示出來了。
12.3.2 NavigationView
目前我們已經(jīng)成功實現(xiàn)了滑動菜單功能,其中滑動功能已經(jīng)做得非常好了,但是菜單卻還很丑,畢竟菜單頁面僅僅使用了一個TextView,非常單調(diào)。有對比才會有落差,我們看一下Google+的滑動菜單頁面長什么樣,如圖12.7所示:
經(jīng)過對比之后是不是覺得我們的菜單頁面更丑了?不過沒關(guān)系,優(yōu)化滑動菜單頁面,這里就是我們本小節(jié)的全部目標。
事實上,你可以在滑動菜單頁面定制任意布局,不過谷歌給我們提供了一種更好的方法---------使用NavigationView。NavigationView是Desgin Support庫中提供的一個控件,他不僅是嚴格按照Material Desgin的要求來設(shè)計的,而且還可以將滑動菜單頁面的視線變得非常簡單。接下來我們就學習一下NavigationView的用法。
? ? ? ? ? ?這里添加了兩行依賴關(guān)系,第一行是Desgin Support庫,第二行是一個開源項目的CircleImageView,它可以用來輕松實現(xiàn)圖片圓形畫的功能,我們待會就會用到它。CircleImageView的項目主頁地址是:https://github.com/hdodenhof/CircleImageView。
在開始使用NavigationView之前,我們還需要提前準備好兩個東西:menu和headerLayout。menu是用來在NavigationView顯示具體菜單項的,headerLayout則是用來在NavigationView中顯示頭布局的。
我們先來準備menu,這里我事先找了幾張圖片作為按鈕圖標,并將它們放在了drawable-xxhdpi目錄下。日前后在右擊menu文件----->NEW---->Menu resource file,創(chuàng)建一個nav_menu.xml文件,并編寫如下代碼:
? ? ? ? ? ?首先我們在<menu>中潛逃了一個<group>標簽,然后將group的chaeckableBehavior屬性指定為single。group表示一個組,checkableBehavior指定為single表示卒中的所有菜單項只能單選。
? ? ? ? ? 那么下面我們來看一下這些菜單項目。這里一共定義了5個item,分別使用android:id屬性指定菜單項的id,android:icon屬性指定菜單項的圖標,android:title屬性指定菜單項顯示文字。就是這么簡單,現(xiàn)在我們已經(jīng)把menu主備好了。
接下來應(yīng)該準備headerLayout了,這是一個可以隨意定制的布局,不過我并不想將它做得太復(fù)雜。這里簡單起見,我們就在headerLayout中放置頭像、用戶名、郵箱地址這3向內(nèi)容吧。
說到頭像,那么我們還需要在準備一張圖片,這里找了一張寵物圖片,并把他放在了drawable-xxhdpi目錄下。另外這張圖片最好是一張正方形圖片,因為待會我們會把它圓形話。然后右擊layout文件夾---->NEW----->layout resource file ,創(chuàng)建一個nav.header.xml文件。修改其中的代碼,如下所示:
? ? ? ? ? ?可以看到,布局文件最外層是一個相對布局,我們將他的寬度設(shè)為match_parent,高度設(shè)為180dp,這是一個NavigationView比較適合的高度,然后指定它的背景色為corlorPrimary。
? ? ? ? ? 在RelativeLayout中我們放置了3個控件,CircleImageView是一個用于將圖片圓形化的控件,它的用法非常簡單,基本和ImageView是完全一樣的,這里給他指定了一張圖片作為頭像,然后設(shè)置為居中顯示。另外兩個TextView分別用于顯示用戶名和郵箱地址,他們都用到了一些Relativelayout的定位屬性,相信肯定難不倒你啦?
? ? ? ? ? ?現(xiàn)在menu和headerLayout都準備好了,我們終于可以使用NavigationView了。修改activity_main.xml中的代碼,如下所示:可以看到,我們將之前的TextView換成了 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?NavigationView,這樣滑動菜單中顯示的內(nèi)容也就變成NavigationView了。這里又通過app:menu和app:headerLayout設(shè)置了進去,這樣NavigationView就定義完成了。
NavigationView雖然定義完成了,但是我們還要去處理菜單項的點擊事件才行。修改MainActivity中的代碼,如下所示:
? ? ? ? ? ?代碼還是比較簡單的,這里首先獲取到了NavigationView的實例,然后再調(diào)用它的setCheckedItem()方法將Call菜單設(shè)置為默認選中。接著調(diào)用了setNavigationItemSelectedListener()方法來設(shè)置一個菜單項選中事件的監(jiān)聽器,當用戶點擊了任意菜單項時,就會調(diào)用onNavigationItemSelected()方法中。我們可以在這個方法中寫相應(yīng)的邏輯處理,不過這里我們并沒有附加任何邏輯,只是調(diào)用了DrawerLayout的closeDrawers()方法將滑動菜單關(guān)閉,這也是合情合理的做法。
現(xiàn)在可以重新運行一下程序,點擊Toolabar左側(cè)的導航按鈕,效果如圖12.8所示。
怎么樣?這樣的滑動菜單頁面,你無論如何也不能說它丑了把?Material Design的魅力就在這里,他真的是一種非常美觀的設(shè)計理念,只要你按照他的各種規(guī)范來設(shè)計界面,最終出來的程序就是特別好看的。
相信你對現(xiàn)在做出來的效果也一定十分滿意吧?不過不要滿足于現(xiàn)狀,后面我們會實現(xiàn)更加炫酷的效果。緊跟腳步,繼續(xù)學習。
12.4 懸浮按鈕和可交互提示
? ? ? ? ? ?立面設(shè)計師Material Design中一條非常重要的設(shè)計思想,也就是說,按照Material Desgin的理念,應(yīng)用程序的界面僅僅只是一個平面,而應(yīng)該是有立體效果。在官方給出的示例中,最簡單且最具代表性的立面設(shè)計就是懸浮按鈕了,這種按鈕不屬于界面平面的一部分,而是位于另外一個維度的,因此就會給人一種懸浮的感覺。
? ? ? ? ? ?本節(jié)中我們會對這個懸浮按鈕的效果進行學習,另外還會學習一種可交互式的提示工具。關(guān)于提示工具,我們之前一直使用Toast,但是Toast只能用于告知用戶某某事情已經(jīng)發(fā)生了,用戶卻不能對此做出任何的響應(yīng),那么今天我們就將這一方面進行擴展。
12.4.14 FolatingActionButton
FolatingActionButton是Desgin Support庫中提供的一個控件,這個空間可以幫助我們比較輕輕松松地實現(xiàn)懸浮按鈕的效果。其實在之前的圖12.2中,我們就已經(jīng)預(yù)覽過懸浮按鈕長什么樣子的了。它默認會使用colorAccent來作為按鈕的顏色,我們呢還可以通過各按鈕指定一個圖標來表明這個按鈕的作用是什么。
下面開始來具體實現(xiàn)。首先仍然需要提前準備好一個圖標,這里我防止了一張ic_done.png到drawable-xxhdpi目錄下。 然后修改activity_mian.xml中的代碼,如下所示:
可以看到,這里我們在主屏幕布局中加入了FolatingActionButton。這個控件的用法并沒有什么特別的地方,layout_with和layout_height屬性都指定城wrap_content,layout_gravity屬性指定將這個控件放置于屏幕的右下角,其中end的工作原理和之前的start是一樣的,即如果系統(tǒng)語言是從左往右的,那么end就表示在右邊,如果系統(tǒng)語言是從右邊往左的,那么end就表示在左邊。然后通過layout_margin屬性給控件的四周留點邊距。經(jīng)貼著屏幕肯定是不好看的,最后通過SRC屬性給FolatingActionButton設(shè)置了一個圖標。
沒錯,就是這么簡單,現(xiàn)在我們就可以運行一下了,效果如圖12.9所示:
一個漂亮的懸浮按鈕就在屏幕的右下方出現(xiàn)了。
如果你仔細觀察的話,會發(fā)現(xiàn)懸浮按鈕的下面還有一地那陰影,其實這很好理解,因為FolatingActionButton是懸浮在當前界面上的,既然是懸浮,那么理所應(yīng)當會有投影,Design ?Support庫連這種細節(jié)都幫我們考慮到了。
說道懸浮,其實我們還可以指定FolatingActionButton的懸浮高度,如下所示:
這里使用了app:elevation屬性來給FolatingActionButton指定一個高度,高度值越大,投影范圍也越大,但是投影效果越淡,高度值越小,投影范圍也越小,但是投影效果越濃,當然這些效果的叉一都不怎么明顯,我個人感覺使用默認的FolatingActionButton效果就已經(jīng)足夠了。
接下來我么看一下FolatingActionButton是如何處理點擊事件的,畢竟,一個按鈕首先要能點擊才有意義。修改MainActivity中的代碼,如下所示:
? ? ? ? ? ? 如果你期待FolatingActionButton會有什么特殊用法的話,那可能就要讓你失望了,它和普通的Button其實沒什么兩樣,都是調(diào)用srtOnClickListener()方法來注冊監(jiān)聽器,當點擊按鈕時,就會執(zhí)行監(jiān)聽器中的onClick()方法,這里我們在onClick()方法中彈出了一個Toast。
? ? ? ? ? ?現(xiàn)在重新運行一下程序,并點擊FolatingActionButton,下過如圖12.10所示。
12.4.2 ? ? ? ? ?Snackbar
? ? ? ? ? 現(xiàn)在我們已經(jīng)掌握了FolatingActionButton的基本用法,不過在一小節(jié)處理點擊事件的時候,仍然是使用Toast來作為提示工具,本小節(jié)中我們就來學習一下Design Support庫提供的更加先進的提示工具---------Snackbar。
? ? ? ? ? 首先要明確,Snackbar并不是Toast的替代品,它們兩者之間有著不同的應(yīng)用場景。Toast的作用是告訴用戶現(xiàn)在發(fā)生了什么事情,但同時用戶只能被動接受這個事情,因為沒有什么辦法能讓用戶進行選擇。而Snackbar則在這方面進行了擴展,他允許在提示當中加入一個可交互按鈕,當用戶點擊按鈕的時候可以執(zhí)行一些額外的邏輯。打個比方,比如,我們在執(zhí)行刪除操作的時候只彈出一個Toast提示,那么用戶要是誤刪了某個重要數(shù)據(jù)的話肯定會十分抓狂吧。但是如果我們增加一個Undo按鈕,就相當于給用戶提供了一種彌補措施,從而大大降低了事故發(fā)生的概率,提升用戶體驗。
? ? ? ? ? Snackbar的用法也非常簡單,它和Toast是基本相似的,只不過可以額外增加一個按鈕的點擊事件。修改MainActivity中的代碼,如下所示:
? ? ? ? ? 可以看到,這里調(diào)用了Snackbar的make方法來創(chuàng)建一個Snackbar對象,make()方法的

? ? ? ? ?第一個參數(shù)要傳入一個View,只要是當前界面布局的任意一個View,都可以,Snackbar會使用這個View來自動查找最外層的布局,用于展示Snackbar,第二個參數(shù)是Snackbar中顯示的內(nèi)容,第三個參數(shù)是Snackbar顯示的時長。這些和Toast都是類似的。
? ? ? ? ?接著這里又調(diào)用了一個setAction()方法來設(shè)置一個動作,從而讓Snackbar不僅僅是一個提示,而是可以和用戶進行交互的。簡單起見,我們在動作按鈕的點擊事件里面彈出了一個Toast提示。最后調(diào)用show()方法讓Snackbar顯示出來。
? ? ? ? ?現(xiàn)在重新運行一下程序,并點擊懸浮按鈕,效果如圖12.11所示。
? ? ? ? 可以看到,Snackbar從屏幕底部出現(xiàn)了,上面有我們所設(shè)置的提示文字,還有一個Undo按鈕,按鈕是可以點擊的。過段時間后Snackbar會自動從底部消失。
? ? ? ? 不管是出現(xiàn)還是消失,Snackbar都是帶有動畫小幅哦的,因此視覺體驗也會比較好。
? ? ? ? 不過你有沒有發(fā)現(xiàn)一個bug,這個Snackbar竟然將我們的懸浮按鈕給遮擋住了。雖說也不是什么重大的問題,因為Snackbar過一會就會自動消失,但這種用戶體驗總歸是不有好的。有沒有什么辦法能解決一下啦?當然有,只需要借助CoordinatorLayout(協(xié)調(diào)員布局)就可以輕松解決。
12.4.3 ? ??CoordinatorLayout(協(xié)調(diào)員布局)
? ? ? ? ? ? CoordinatorLayout(協(xié)調(diào)員布局)可以說是一個加強版的FrameLayout,這個布局也是由Design Support庫提供的。他的普通情況下的作用和FrameLayout基本一致,不過既然是Design Support庫提供的布局,那么就必然有一些Material Design的魔力了。
? ? ? ? ? ? 事實上,CoordinatorLayout可以監(jiān)聽其所有子空間的各種事件,然后自動幫助我們做出最為合理的響應(yīng)。舉個簡單的例子,剛才彈出的Snackbar提示將懸浮按鈕遮擋住了,而如果我們能讓CoordinatorLayout(協(xié)調(diào)員布局)監(jiān)聽到Snackbar的彈出事件,那么他會自動將內(nèi)部的
? ? ? ? ? ?FolatingActionButton向上偏移,從而確保不會被Snackbar遮擋住。
? ? ? ? ? 至于CoordinatorLayout的使用也非常簡單,我們只需要將原來的的FrameLayout替換一下就可以了。修改activity_main.xml中的代碼,如下所示:

? ? ? ? ? ?由于CoordinatorLayout本省就是一個加強版的FrameLayout,因此這種替換不會又仍和的副作用?,F(xiàn)在重新運行一下程序,并點擊懸浮按鈕,效果如圖12.12所示:
? ? ? ? ? ?可以看到,懸浮按鈕自動向上偏移了Snackbar的同等高度,從而確保不會被遮擋住,當Snackbar消失的時候,懸浮按鈕會自動向下偏移回原來的位置。
? ? ? ? ? ?另外懸浮按鈕的向上和向下偏移也是伴隨著動畫效果的,且和Snackbar完全同步,整體效果看上去特別賞心悅目。
? ? ? ? ? ?不過我們回過頭來再思考一下,剛才說的是CoordinatorLayout可一件挺其他所有子空間的各種事件,但是Snackbar好像并不是CoordinatorLayout的子空間吧!為什么他卻可以被監(jiān)聽到啦?
? ? ? ? ? ?其實道理很簡單,還記得我們在Snackbar的make()方法中傳入的第一個參數(shù)嗎?整個參數(shù)就是用來指定Snackbar是基于哪個View來觸發(fā)的,剛才我們傳入的是FloatingActionButton本身,而FloatingActionButton是CoordinatorLayout中的子空間,因此這個事件就理所應(yīng)當能被監(jiān)聽到了。你可以自己再做個實驗,如果給Snackbar的make()方法傳入一個DrawerLayout,那么Snackbar就會再次遮擋主懸浮按鈕,因為DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout也就無法監(jiān)聽到Snackbar的演出和隱藏事件了。
? ? ? ? ? ?本節(jié)的內(nèi)容就到這里,接下來我們繼續(xù)豐富MaterialTest項目,加入卡片式布局效果。
12.5 卡片式布局
雖然現(xiàn)在MaterialTest已經(jīng)用了非常多的Material Design效果,不過你會發(fā)現(xiàn)沒界面上最主要的一塊區(qū)域還是處于空白狀態(tài)。這一塊區(qū)域通常都是用來放置應(yīng)用程序的主體內(nèi)容,握住杯使用一些精美的說過圖片填充這部分區(qū)域。
那么為了要讓水果圖片也能Material化,本節(jié)中我們將會學習如何實現(xiàn)卡片式布局的效果??ㄆ讲季忠彩荕aterial Design中提出的一個型概念,也可以讓頁面中的元素看起來就像在看片中一樣,并且還能擁有圓角和投影,下面我們就開始具體的學習一下。
12.5.1 ? CardView
CardView是用于實現(xiàn)卡片布局效果的重要空間,有appcompat-V7庫提供。實際上CardView也是一個FrameLayout,只是額外提供了圓角和陰影等效果,看上去會有立體的感覺。
我們先來看一下CardView的基本用法吧,其實非常簡單,如下所示:
這里定義了一個CardView布局,我們可以通過app:cardCornerRadius屬性指定卡片圓角的弧度,數(shù)值越大,圓角的弧度也越大。另外還可以通過app:elevation屬性指定卡片的高度,高度值越大,投影范圍也越大,但是投影效果越淡,高度值越小,投影范圍也越小,但是投影效果越濃,這一點和FloatingActionButton是一致的。
然后我們在CardView布局中防止了一個TextView,那么這個TextView就會顯示在一張卡片當中了,CardView的用法就這么簡單。
但是我們顯然不可能在如此寬闊的一塊空白區(qū)域內(nèi)只放置一張卡片,為了能夠從分利用屏幕的控件,這里我準備綜合運用下第三章中學到的知識,使用RecycleView來填充MaterialTest項目的主界面部分。還記得之前實現(xiàn)過的水果列表效果嗎?這次我們將升級一下,實現(xiàn)一個高配版的水果列表效果。
既然是要實現(xiàn)水果列表,那么首先肯定需要準備許多張水果圖片,這里必須在app/build.gradle文件中聲明這些庫的以來才行:(需要用到的控件就將包添加進去這里主要是recycleview和CradView)


注意上訴聲明的最后一行,這里添加了一個Glide庫的依賴。Glide是一個超級強大的圖片加載庫,它不僅可以用于加載本地圖片,還可以加載網(wǎng)絡(luò)圖片、GIF圖片、甚至是本地視頻。最重要的是,Glide得用法非常簡單,只需要一行代碼就能輕松實現(xiàn)復(fù)雜的圖片加載功能,因此這里我們準備用它來加載水果圖片。
接下來開始具體的代碼實現(xiàn),修改activity_main.xml中的代碼,如下所示:

這里我們在CoordinatorLayout中添加了一個Recycleview,給它制定一個id,然后將寬度和高度都設(shè)置為match_parent,這樣Recycleview也就占滿了布局空間。
接著定義一個實體類Fruit,代碼如下所示:

Fruit類中只有兩個字段,name表示水果的名字,imageId表示水果對應(yīng)圖片的資源id。
然后需要為Recycleview的指向指定一個我們自定義的布局,在Layout目錄下新建fruilt_item.xml,代碼如下:


? ? ? ? ? ? 這里使用了CradView來作為子項最外層布局,從而使得RecycleView中的每個元素都是在卡片當中。CradView由于是一個FrameLayout,因此他沒有什么方便的地位方式,這里我們只好在CradView外層嵌套了一個LinearLayout,然后在LinearLayout中放置具體的內(nèi)容。
? ? ? ? ? ?內(nèi)容倒也沒什么特殊的地方,就是定義一個ImageView用于顯示水果的圖片,又定義了一個TextView用于顯示水果的名稱,并讓TextView在水平方向上居中顯示。注意哦ImageView使用了scaleType屬性,這個屬性可以指定圖片的縮放模式。由于個張水果圖片的長寬比例可能都不一致,為了讓所有的圖片都能充滿整個ImageView,這里使用了centerCrop模式,它可以讓圖片保持原有的比例填充滿ImageView,并將超出屏幕的部分裁減掉。
? ? ? ? ? 接下來需要為RecycleView準備一下適配器,新建FruitAdapter類,讓這個適配器繼承自RecycleView.Adapter,并將泛型指定為FruitAdapter.ViewHolder,代碼如下所示:


? ? ? ? ? 上訴代碼代碼相信你一定很熟悉,和我們在第三章中編寫的FruitAdapter幾乎一摸一樣。唯一需要注意的是onBindViewHolder()方法中使用了Glide來加載水果圖片。
? ? ? ? ? 那么這里就順便來看心意啊Gilder.with()方法并傳入一個Context、Activity或是Fragment參數(shù),然后調(diào)用load()方法去加載圖片,可以是一個URL地址,也可以是一個本地路徑,或者是一個資源Id,最后調(diào)用into()方法將圖片設(shè)置到具體某一個ImageView中就可以了。
? ? ? ? ? ? ? ? ?那么我們?yōu)槭裁匆褂肎lide而不是傳統(tǒng)的設(shè)置圖片方式啦?因為這次我從網(wǎng)上找到的這些水果圖片像素都非常高,如果不進行壓縮就直接展示的話,很容易引起內(nèi)存溢出。而使用Glide就完全不需要擔心著回事,因為Glide在內(nèi)部做了許多非常復(fù)雜的邏輯操作,其中就包括了圖片壓縮,我們只需要安心按照Glide的標準用法去加載圖片就可以了。
? ? ? ? ? ? ? ? ?這樣我們就將RecycleView的適配器準備好了,最后修改MainActivity中的代碼,如下所示:
? ? ? ? ? ? ?數(shù)據(jù):


控件綁定----->(6什么6)基本操作

? ? ? ? ?在MianActivity中我們首先定義了一個數(shù)組,數(shù)組里面存放了很多個Fruit的實例,每個實例都代表這一種水果。然后在initView()方法中,顯示清空了一下fruitList中的數(shù)據(jù),接著使用一個隨機函數(shù),從剛才定義的Fruit數(shù)組中隨機挑選一個水果方法到fruitList當中,這樣每次打開程序看到的水果數(shù)據(jù)都會是不同的。另外,為了讓界面上的數(shù)據(jù)多一些,這里使用了一個循環(huán),隨機挑選50個水果。
? ? ? ? ?之后的用法就是RecycleView的標準用法了,不過這里使用了GridLayoutManager這種布局方式。在第三章中我們已經(jīng)學過了LinearLayoutManager的用法也沒什么特別之處,他的構(gòu)造函數(shù)接收兩個參數(shù),第一個Context,第二個參數(shù)是列數(shù),這里我們希望每一行中有兩列數(shù)據(jù)。
? ? ? ? ?現(xiàn)在重新運行一下程序,效果如圖12.13所示:
? ? ? ? ?可以看到,精美的水果圖片功能展示出來了。每個水果都是在一張單獨的卡片當中,并且還擁有圓角和投影,是不是非常美觀?另外,由于我們是使用隨機的方式獲取水果數(shù)據(jù)的,因此界面上回有一些重復(fù)的水果出現(xiàn)。這屬于正?,F(xiàn)象。
? ? ? ? 當你陶醉于當前精美的界面的時候,你是不是忽略了一個細節(jié)?哎呀,我們呢的Toolbar怎么不見了!仔細觀察一下原來是被RecycleView給擋住了。這個問題又該怎么解決啦?這就需要借助到另外一個工具了--------AppBarLayout。
12.5.2 ? ? ? ? AppBarLayout
? ? ? ? ? ? ? 首先我們先來分析一下為什么RecycleView會把ToolBar給遮擋住吧。其實并不難理解,由于RecycleView和Toolbar都是放置在CoordinatorLayout中的,而前面已經(jīng)說過,CoordinatorLayout就是一個加強版的FrameLayout,那么FrameLayout中的所有控件在不進行明確定位的情況下,默認都會擺放在布局的左上角,從而也就產(chǎn)生了遮擋的現(xiàn)象。其實這已經(jīng)不是你第一次遇到這種情況了,我們在3.3.3小節(jié)學習FrameLayout的時候就早早見識過了控件與控件之間遮擋的效果。
? ? ? ? ? ? ? 既然已經(jīng)知道了問題的原因,那么該如何解決啦?傳統(tǒng)情況下,使用偏移是唯一的解決辦法,既讓RecycleView向下偏移一個Toolbar的高度,從而保證不會遮擋到Toolbar。不過我們使用的并不是普通的FrameLayout,而是CoordinatorLayout,因此自然會有一些更加巧妙的解決辦法。
? ? ? ? ? ? ? 這里我準備使用Design Support庫中提供了另外一個工具---------AppBarLayout。AppBarLayout實際上是一個垂直方向的LinearLayout,他在內(nèi)部做了很多滾動時間的封裝,并應(yīng)用了一些Material Design的設(shè)計理念。
? ? ? ? ? ? ?那么我們怎樣使用AppBarLayout才能解決前面覆蓋的問題啦?其實只需要兩步就可以了,第一步將Toolbar嵌套到AppBarLayout,第二步給RecycleView制定一個布局行為。修改activity_main.xml中的代碼,如下所示:

? ? ? ? ? 可以看到,布局文件并沒有什么太大的變化。我們首先定義了一個AppBarLayout,并將Toolbar放置在了AppBarLayout里面,然后在RecycleView中使用app:layout_behavior屬性指定了一個布局行為。其中appbar_scrolling_view_behavior這個字符串也是由Design Support庫提供的。
? ? ? ? ? 現(xiàn)在重新運行一下程序,你就會發(fā)現(xiàn)一切正常了,如圖12.14所示。
? ? ? ? ? 雖說使用AppBarLayout已經(jīng)成功解決了RecycleView遮擋ToolBar的問題,但是剛才有提到過,說AppBarLayout中應(yīng)用了一些Material Design的設(shè)計理念,好像從上面的例子完全體現(xiàn)不出來呀。事實上,當RecycleView滾動的時候就已經(jīng)滾動事件都通知給了AppBarLayout了,只是我們還沒處理而已。那么下面就讓我們來進一步優(yōu)化,看看AppBarLayout到底能實現(xiàn)什么樣的Material Design效果。
當AppBarLayout接收到滾動事件的時候,它內(nèi)部的子控件其實是可以指定如何去影響這些事件的,通過app:layout_scrollFlags屬性就能實現(xiàn)。修改activity_main.xml中的代碼,如下所示:

? ? ? ? ? ? 這里在Toolbar中添加了一個app:layout_scrollFlags屬性,并將這個屬性的值指定了scroll|enterAlways|snap。其中scroll表示當RecycleView向上滾動的時候,Toolbar會跟著一起向上滾動并實現(xiàn)隱藏;enterAlways表示當RecycleView向下滾動的時候,Toolbar會跟著向下滾動并顯示。snap表示當Toolbar還沒有完全隱藏或顯示的時候,會根據(jù)當前滾動的距離,自動選擇是隱藏還是顯示。
? ? ? ? ? ? 我們要改動的就只有一行代碼而已,現(xiàn)在重新運行一下程序,并向上滾動RecycleView,效果如圖12.15所示
? ? ? ? ? ? 可以看到,隨著我們向上滾動RecycleView,Toolbar竟然消失了,而向下滾動RecycleView,Toolbar又會重新出現(xiàn)。這其實也是Material Design中的一項重要思想,因為當用戶在向上滾動RecycleView的時候,其注意力肯定是在RecycleView的內(nèi)容上面的內(nèi)容上面的,這個時候如果Toolbar還占據(jù)這屏幕控件,就會在一定程度上影響用戶的閱讀體驗,而將Toolbar隱藏則可以讓閱讀體驗達到最佳狀態(tài)。當用戶保證了用戶的最佳閱讀效果,又不影響任何功能上的操作,Material Design考慮的就是這么細致入微。
? ? ? ? ? ? ?當然了,像這種功能,如果是使用ActionBar的話,那就完全不可能實現(xiàn)了,Toolabar的出愛心為我們提供了更多的可能。
12.6 ? ?下拉刷新
? ? ? ? ? 下拉刷新這種功能早就不是什么新鮮的東西了,幾乎所有的應(yīng)用里都有這個功能。不過市面上現(xiàn)有的下拉刷新功能在風格上都各有不相同,并且和Material Desgin還有些格格不入的感覺。因此,谷歌為了讓Android的下拉刷新風格能有一個統(tǒng)一的標準,于是在Matreial Design中制定了一個官方的設(shè)計規(guī)范。當然,我們并不需要去深入了解這個規(guī)范到底是什么樣的,因為谷歌早就提供好了縣城空間,我們只需要在項目中直接使用就可以了。
? SwipeRefreshLayout就是用于實現(xiàn)下拉刷新功能的核心類,它是由support-v4庫提供的。我們把想要實現(xiàn)下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速的讓這個控件支持下拉刷新,那么在MaterialTest項目中,應(yīng)該支持下拉刷新功能的控件自然就是RecycleView了。
? ? ? ? ? ?由于SwipeRefreshLayout的用法也比較簡單,下面我們就直接開始使用了,修改activity_main.xml中的代碼,如下所示:

? ? ? ? ? ? 可以看到,這里我們在RecycleView的外面又嵌套了一層SwipeRefreshLayout,這樣RecycleView就自動擁有下拉刷新功能了。另外需要注意,由于RecycleView現(xiàn)在變成了SwipeRefreshLayout的子控件,因此之前使用app:layout_behavior聲明的布局行為現(xiàn)在也要移動到SwipeRefreshLayout才行。
? ? ? ? ? ?不過這還沒有結(jié)束,雖然RecycleView已經(jīng)支持了下拉刷新功能了,但是我們還要在代碼重處理具體的刷新邏輯才行。修改MainActivity中的代碼,如下所示:



? ? ? ? ? ? 這段代碼應(yīng)該還是比較好理解的,首先通過findViewById()方法拿到SwipeRefreshLayout的實例,然后調(diào)用setColorSchemeResources()方法來設(shè)置下拉刷新進度條的顏色,這里我們就使用主題中的colorPrimary作為進度條的顏色了。接著調(diào)用setOnRefreshLishtener()方法來設(shè)置一個刷新的監(jiān)聽器,當觸發(fā)了下拉刷新操作就會回調(diào)這個間天氣的onRefresh()方法,然后我們在這里去處理具體的數(shù)顯邏輯就可以了。
? ? ? ? ? ? 通常情況下,onResfresh()方法中應(yīng)該是去網(wǎng)絡(luò)上請求最新的數(shù)據(jù),然后再將這些數(shù)據(jù)展示出來。這里簡單起見,我們就不和網(wǎng)絡(luò)進行交互了,而在調(diào)用一個refreshFruilts()方法進行本地刷新操作。refreshFruilts()方法中線是掀起了一個線程,然后將線程沉睡兩秒鐘,之所以這樣做,是因為本地刷新的過程速度非常快 ,如果不將線程沉睡的話,刷新立刻就會結(jié)束了,從而看不到刷新的過程。沉睡結(jié)束之后,這里使用了runOnUiThread()方法將線程切換回主線程,然后調(diào)用 ininView()方法重新生成重新生成數(shù)據(jù),接著再調(diào)用fruiltAdapter的notifyDataSetChanged()方法通知數(shù)據(jù)發(fā)生了變化,最后調(diào)用SwipeRefreshLayout的setRefreshing()方法并傳入flase,用于表示刷新事件結(jié)束,用于表示刷新事件結(jié)束,并隱藏刷新進度條。
? ? ? ? ? ?現(xiàn)在可以重新運行一下程序了,在屏幕的主界面向下拖動,會有一個下拉刷新的進度條出現(xiàn)。松手后就會自動進行刷新了,效果如圖:12.16所示:
? ? ? ? ? ?下拉刷新的進度條只會停留兩秒,之后就會自動消失了并且這個就是Material Design中滾定的最標準的效果,Design Support苦衷的常用控件也學了大半了。不過本章的學習之旅還沒有結(jié)束,在最后的尾聲部分,我明年再來實現(xiàn)一個非常震撼的Material Design效果-------可折疊式的標題欄。
12.7 ? ? ? 可折疊式的標題欄
? ? ? ? ? ? 雖說我們現(xiàn)在的標題欄時使用Toolbar來編寫的,不過他看上去和傳統(tǒng)的ActionBar其實沒有什么兩樣,只不過可以響應(yīng)RecycleView的滾動事件來進行隱藏和顯示。而Material Design中并沒有限定標準欄必須長什么樣子的,事實上,我們可以根據(jù)自己的喜愛隨意定制標題欄的樣式。那么本節(jié)中我們就來實現(xiàn)一個可折疊式標題欄的效果,需要借助到CollapsingToolbarLayout這個工具。
12.7.1? CollapsingToolbarLayout
? ? ? ? ? ? ? 顧名思義CollapsingToolbarLayout是一個作用于Toolabar基礎(chǔ)上的布局,他也是有Design Support庫提供的。CollapsingToolbarLayout可以讓Toolbar的效果變得更加豐富,不僅僅是展示一個標題欄,而是能夠?qū)崿F(xiàn)非常華麗的效果。
不過,CollapsingToolbarLayout是不能獨立存在的,它在設(shè)計的時候就被限定只能作為AppBarLayout的直接子布局來使用。而AppBarLayout又必須是使用CoordinatorLayout的子布局,因此本節(jié)我們要實現(xiàn)的功能其實需要綜合運行前面所學的知識。那么話不多說,這就開始吧!
? ? ? ? ? 首先需要一個格外的活動作為水果詳情頁的展示界面,右擊com.exmple.materialtest包------------>New------->Activity-------->Empty Activity,創(chuàng)建一個FruiltActivity,并將布局名指定成activity_fruit.xml,然后我們開始編寫水果詳情頁展示界面的布局。
? ? ? ? ?由于整個布局文件比較復(fù)雜,這里我準備采用分段編寫的方式。activity_fruit.xml中的內(nèi)容主要分為兩部分,一個是水果標題欄,一個是水果內(nèi)容詳情,我們來一步步實現(xiàn)。
首先實現(xiàn)標題欄部分,這里使用CoordinatorLayout來作為最外層布局,如下所示: