Android學習之基礎知識十五 — 最佳UI體驗(Material Design實戰)


一、前言

  長久以來,大多數人都認為Android系統的UI並不美觀,至少沒有iOS系統的美觀。以至於很多IT公司在進行應用界面設計的時候,為了保證雙平台的統一性,強制要求Android端的界面風格必須和iOS端一致,這種情況在現實工作中實在是太常見了,因為對於一般用戶來說,他們不太可能會在兩個操作系統上分別去使用同一個應用,但是卻必定會在同一個操作系統上使用不同的應用。因此同一個操作系統中各個應用之間的界面統一性要遠比一個應用在雙平台的界面統一性重要的多,只有這樣,才能給使用者帶來更好的用戶體驗。

  但是問題在於,Android標准的界面設計風格並不是特別被大眾所接受,很多公司都覺得自己完全可以設計出更好看的界面,從而導致Android平台的界面風格長期難以得到統一。為了解決這個問題,谷歌在2014年Google I/O大會上重磅推出了一套全新的界面設計語言——Material Design。

二、什么是Material Design

  Material Design是由谷歌的設計工程師們基於傳統優秀的設計原則,結合豐富的創意和科學技術所發明的一套全新的界面設計語言,包含了視覺、運動、互動效果等特性。那么谷歌憑什么認為Material Design就能解決Android平台界面風格不統一的問題呢?一言以蔽之,好看。

  這次谷歌在界面設計上確實下了大功夫,很對媒體評論,Material Design的出現使得Android首次在UI方面超越了iOS,按照正常的思維來想,如果各個公司都無法設計出比Material Design更加出色的界面風格,那么它們就應該理所應當的使用Material Design來設計界面,從而解決了Android平台界面風格不統一的問題了。

  為了做出表率,谷歌從Android5.0系統開始,就將所有內置的應用都使用Material Design風格來進行設計。不過,在重磅推出之后,Material Design的普及程度卻不能說是特別理想,因為這只是一個推薦的設計規范,主要是面向UI設計人員的,而不是面向開發者的。很多開發者可能根本就搞不清楚什么樣的界面和效果才叫Material Design,就算搞清楚了,實現起來也會很費勁,因為不少Material Design的效果是很難實現的,而Android中卻幾乎沒有提供相應的API支持,一切都要靠開發者自己從零寫起。

  谷歌當然也意識到了這個問題,於是在2015年的Google I/O大會上推出了一個Design Support庫,這個庫將Material Design中最具代表性的一些控件和效果進行了封裝,使得開發者在即使不了解Material Design的情況下也能非常輕松的將自己的應用Material化。下面我們就針對Design Support這個庫進行深入的學習,並且配合一些其他的控件來完成一個優秀的Material Design應用。

  先新建一個MaterialTest項目:

三、Toolbar

  Toolbar將會是我們接觸的第一個Material控件。雖說對於Toolbar暫時還是比較陌生,但是對於它的另一個相關控件ActionBar,就應該有點熟悉了。前面有一節為了使用一個自定義的標題欄,而把系統原生的ActionBar隱藏掉,每個活動最頂部的那個標題欄其實就是ActionBar,之前我們編寫的所有程序里一直都有ActionBar的身影。

  不過ActionBar由於其設計的原因,被限定只能位於活動的頂部,從而不能實現一些Material Design的效果,因此官方現在已經不再建議使用ActionBar了,而是更加推薦Toolbar。

  Toolbar的強大之處在於,它不僅繼承了ActionBar的所有功能,而且靈活性很高,可以配合其他控件來完成一些Material Design的效果,下面我們就來具體學習一下:

  任何一個新建的項目,默認都是會顯示ActionBar的,那么ActionBar到底是從哪里來的呢?其實這是根據項目中指定的主題來顯示的,打開AndroidManifest文件查看:

  可以看到,這里使用android:theme屬性指定了一個AppTheme的主題,那么這個AppTheme又是在哪里定義的呢?打開:res/values/styles.xml文件,代碼如下所示:

  這里定義了一個叫AppTheme的主題,然后指定它的parent主題是:Theme.AppCompat.Light.DarkActionBar。這個DarkActionBar是一個深色的ActionBar主題,我們之前所有的項目中自帶的ActionBar就是因為指定了這個主題才出現的。

  而現在我們准備使用Toolbar來提到ActionBar,因此需要指定一個不帶ActionBar的主題,通常有:Theme.AppCompat.NoActionBar和Theme.AppCompat.Light.NoActionBar這兩種主題可選。其中Them.AppCompat.NoActionBar表示深色主題,它會將界面的主題顏色設成深色,陪襯顏色設成淡色。而Theme.AppCompat.Light.NoActionBar表示淡色主題,它會將界面的主題顏色設成淡色,陪襯顏色設成深色。具體的效果你可以動手試試,這里由於我們之前的程序一直都死以淡色為主,那就選用淡色主題了,如下所示:

  然后觀察一下AppTheme中的屬性重寫,這里重寫了colorPrimary、colorPrimaryDark、colorAccent這3個屬性的顏色,那么這3個屬性分別代表着位置什么顏色呢?可以在Android Studio左邊看到顏色,位置如圖:

  除了上述3個屬性之外,我們還可以通過textColorPrimary、windowsBackground和navigationColor等屬性來控制更多位置的顏色,不過唯獨colorAccent這個屬性比較難理解,它不只是用來指定這樣一個按鈕的顏色,而且更多表達了一個強調的意思,比如一些控件的選中狀態也會使用colorAccent的顏色。

  現在我們已經將ActionBar隱藏起來了,接下來使用Toolbar來替代ActionBar:

第一步:修改activity_main.xml中的代碼:

  雖然這段代碼不長,但是里面着實有不少技術點是需要我們去仔細琢磨的。首先看一下第三行,這里使用了xmlns:app指定了一個新的命名空間,思考一下,正是由於每個布局文件都會使用xmlns:android來指定一個命名空間,因此才能一直使用android:id、android:layout_width等寫法,那么這里指定了xmlns:app,也就是說現在可以使用app:attribute這樣的寫法了,但是為什么這里要指定一個xmlns:app的命名空間呢?這是由於Material Design是在Android5.0系統中才出現的,而很多的Material屬性在5.0之前的系統中並不存在,那么為了能夠兼容之前的老系統,我們就不能使用android:attribute這樣的寫法了,而是應該使用app:attribute。

  接下來定義了一個Toolbar控件,這個控件是由appcompat-v7庫提供的。這里我們給Toolbar指定了一個id,將它的寬度設置為match_parent,高度設置為actionBar的高度,背景顏色設置為colorPrimary。不過下面的部分就稍微有點難理解了,由於我們剛才在styles.xml中將程序的主題指定成了淡色主題,因此Toolbar現在也是淡色主題,而Toolbar上面的各種元素就會自動使用深色系,這是為了和主體顏色區別開。但是這個效果看起來就會很差,之前使用ActionBar時文字都是白色的,現在變成黑色的會很難看。那么為了能讓Toolbar單獨使用深色主題,這里我們使用android:theme屬性,將Toolbar的主題指定成了ThemeOverlay.AppCompat.Dark.ActionBar。但是這樣指定完了之后又會出現新的問題,如果Toolbar中有菜單按鈕,那么彈出的菜單也會變成深色主題,這樣就再次變得十分難看,於是這里使用了app:popupTheme屬性單獨將彈出的菜單項指定為淡色主題,之所以使用app:popupTheme,是因為popupTheme這個屬性是在Android5.0系統中新增的,我們使用app:popupTheme的話就可以兼容Android5.0以下的系統了。

  如果你覺得上面的描述很繞的話,可以自己動手做一做實驗,看看不指定上述主題會是什么樣的效果,這樣你會理解得更加深刻。

第二步:修改MainActivity中的代碼,注意:這里Toolbar有兩個引用包,選擇:android.support.v7.widget.Toolbar包

  這里關鍵的代碼只有兩句,首先通過findViewById()得到Toolbar的實例,然后調用setSupportActionBar()方法並將Toolbar的實例傳入,這樣我們就做到既使用了Toolbar,又讓它的外觀與功能都和ActionBar一致了。

第三步:運行程序,效果如下:

  這個標題欄我們再熟悉不過了,雖然看上去和之前的標題沒什么兩樣,但其實它已經是Toolbar而不是ActionBar了,因此它現在也具備了實現Material Design效果的能力,這個我們在后面就會學到。

  接下來我們再學習一些Toolbar比較常用的功能吧,比如修改標題欄上顯示的文字內容,這段文字內容是在AndroidManifest.xml中指定的,如下所示:

 

  這里給activity增加了一個android:label屬性,用於指定在Toolbar中顯示的文字內容,如果沒有指定的話,會默認使用application中指定的label內容,也就是我們的應用名稱。

  不過只有一個標題的Toolbar看起來太單調了,我們還可以再添加一些action按鈕來讓Toolbar更加豐富一些,這里我提前准備了幾張圖片來作為按鈕的圖標,將它們放置在了mipmap-xxhdpi目錄下,現在右擊res目錄 —> New —> Directory,創建一個menu文件夾。然后右擊menu文件夾 —> New —> Menu resource file,創建一個toolbar.xml文件,並編寫如下代碼:

  可以看到,我們通過<item>標簽來定義action按鈕,android:id用於指定按鈕的id,android:icon用於指定按鈕的圖標。android:title用於指定按鈕的文字。

  接着使用app:showAsAction來指定按鈕的顯示位置,之所以這里再次使用了app命名空間,同樣是為了能夠兼容低版本的系統。showAsAction主要有以下幾種值可選:always表示永遠顯示在Toolbar中,如果屏幕空間不夠則不顯示;ifRoom表示屏幕空間足夠的情況下顯示在Toolbar中,不夠的話就顯示在菜單當中;never表示永遠顯示在菜單當中。注意,Toolbar中的action按鈕只會顯示圖標,菜單中的action按鈕只會顯示文字。

  接下來修改MainAction中的代碼:

  非常簡單,我們在onCreateOptionMenu()方法中加載了toolbar.xml這個菜單文件,然后在onOptionsItemSelected()方法中處理各個按鈕的點擊事件。重新運行程序,效果如下:

  可以看到,Toolbar上面現在顯示了兩個action按鈕,這是因為Backup按鈕指定的顯示位置是always,Delete按鈕指定的顯示位置是ifRoom,而現在屏幕空間很充足,因此這兩個按鈕都會顯示在Toolbar中,另外一個Settings按鈕由於指定的顯示位置是never,所以不會顯示在Toolbar中,點擊一下最右邊的菜單按鈕來展開菜單項,你就能找到Settings按鈕了。另外這些action按鈕都是可以響應點擊事件的。

四、滑動菜單

  滑動菜單可以說是Material Design中最常見的效果之一了,在許多著名的應用(如Gmail、Google+等)中,都是滑動菜單的功能。雖說這個功能看上去好像挺復雜的,不過借助谷歌提供的各種工具,我們可以很輕松的實現非常炫酷的滑動菜單效果。

4.1、DrawerLayout

   所謂的滑動菜單就是將一些菜單項隱藏起來,而不是放置在主屏幕上,然后可以通過滑動的方式將菜單顯示出來。這種方式既節省了屏幕空間,又實現了非常好的動畫效果,是Material Design中推薦的做法。

  不過如果我們全靠自己去實現上述功能的話,難度很大。幸運的是,谷歌提供了一個DrawerLayout控件,借助這個控件,實現滑動菜單簡單又方便。

  先簡單介紹一下DrawerLayout的用法,首先它是一個布局,在布局中允許放入兩個直接子控件,第一個子控件是主屏幕中顯示的內容,第二個子控件是滑動菜單中顯示的內容,因此,我們就可以對activity_main.xml中的代碼做如下修改:

  可以看到,這里最外層的控件使用了DrawerLayout,這個控件是由support-v4庫提供的。DrawerLayout中放置了兩個直接子控件,第一個子控件是FrameLayout,用於作為主屏幕中顯示的內容,當然里面還有我們剛剛定義的Toolbar,第二個子控件這里使用了一個TextView,用於作為滑動菜單中顯示的內容,其實使用什么都可以,DrawerLayout並沒有限制只能使用固定的控件。

  但是關於第二個子控件有一點需要注意,layout_gravity這個屬性是必須指定的,因為我們需要告訴DrawerLayout滑動菜單是在屏幕的左邊還是右邊,指定left表示滑動菜單在左邊,指定right表示滑動菜單在右邊,這里指定的是start,表示會根據系統語言進行判斷,如果系統語言是從左往右的, 比如英語、漢語,滑動菜單就在左邊,如果系統語言從右往左的,比如阿拉伯語,滑動菜單就在右邊。

  現在運行一下程序,,然后在屏幕的左側邊緣向右拖動,就可以讓滑動菜單顯示出來了:

  然后向左滑動菜單,或者點擊一下菜單以外的區域,都可以讓滑動菜單關閉,從而回到主界面。無論是展示還是隱藏滑動菜單,都是非常流暢的動畫過渡的。

  可以看到,我們只是稍微改動了一下布局文件,就能實現如此炫酷的效果,不過現在的滑動菜單還是有點問題,因為只有在屏幕的左側邊緣進行拖動時才能將菜單拖出來,而很多用戶根本不知道有這個功能,那么這么提示他們呢?

  Material Design建議的做法就是在Toolbar的最左邊加入一個導航按鈕,點擊按鈕也會將滑動菜單的內容展示出來,這樣就相當於給用戶提供了兩種打開滑動菜單的方式,防止一些用戶不知道屏幕的左側邊緣是可以拖動的。下面我們開始來實現這個功能:

第一步:首先要准備一張導航按鈕的圖標:ic_menu.png,將它放在mipmap-xxhdpi目錄下。

第二步:修改MainActivity中的代碼:

  這里我們並沒有改動多少代碼,首先調用了findViewById()方法得到了DrawerLayout的實例,然后調用getSupportActionBar()方法得到了ActionBar的實例,雖然這個ActionBar的具體實現是由Toolbar來完成的,接着調用ActionBar的setDisplayHomeAsUpEnabled()方法讓導航按鈕顯示出來,又調用了setHomeAsUpIndicator()方法來設置一個導航按鈕圖標,實際上,Toolbar最左側的這個按鈕就叫作HomeAsUp按鈕,它默認的圖標是一個返回的箭頭,含義是返回上一個活動。很明顯,在這里我們將它默認的樣式和作用都進行了修改。

  接下來在onOptionsItemSelected()方法中對HomeAsUp按鈕的點擊事件進行處理,HomeAsUp按鈕的id永遠都是:android.R.id.home。然后調用DrawerLayout的openDrawer()方法將滑動菜單展示出來,注意openDrawer()方法要求傳入一個Gravity參數,為了保證這里的行為和XML中定義的一致,我們傳入了GravityCompat.START。

第三步:現在重新運行程序,效果如下所示:點擊小球,滑動菜單就會再次展示出來了。

  

4.2、NavigationView

   目前我們已經成功實現了滑動菜單功能,其中滑動功能已經做得非常好了,但是菜單卻還是很丑,畢竟菜單頁面僅僅使用了一個TextView,非常單調。事實上,你可以在滑動菜單頁面定制任意的布局,不過谷歌給我們提供了一種更好的方法——NavigationView。NavigationView是Design Support庫中提供的一個控件,它不僅是嚴格按照Material Design的要求來進行設計的,而且還可以將滑動菜單頁面的實現變得非常簡單。接下來就來學習一下NavigationView的用法。

第一步:既然這個控件是Design Support庫中提供的,那么就需要將這個庫引入到項目中才行。打開app/build.gradle文件,在dependencies閉包中添加如下內容:

這里添加了兩行依賴關系,第一行就是Design Support庫,第二行是一個開源項目CircleImageView,它可以用來輕松實現圖片圓形化的功能,我們待會就會用到它。CircleImageView的項目主頁地址是:https://github.com/hdodenhof/CircleImageView.

  在開始使用NavigationView之前,我們還需要提前准備好兩個東西:menu和headerLayout。menu是用來在NavigationView中顯示具體的菜單項的,headerLayout則是用來在NavigationView中顯示頭部布局的。

  我們先來准備menu,這里我事先找了幾張圖片來作為按鈕的圖標,並將它們放在了mipmap-xxhdpi目錄下。然后右擊menu文件夾 —> New —> Menu resource file,創建一個nav_menu.xml文件,編寫如下代碼:

  我們首先在<menu>中嵌套了一個<group>標簽,然后將group的checkableBehavior屬性指定為single。group表示一個組,checkableBehavior指定為single表示組中的所以菜單項只能單選。

  那么接下來我們就看一下這些菜單項吧。這里一共定義了5個item,分別使用android:id屬性指定菜單項的id,android:icon屬性指定菜單項的圖標,android:title屬性指定菜單項顯示的文字。現在我們就已經把menu准備好了。

  接下來應該准備headerLayout了,這是一個可以隨意定制的布局,為了簡單起見,我們就在headerLayout中放置頭像、用戶名、郵箱地址這3項內容。這里找了一些寵物頭像,並把它放置在了mipmap-xxhdpi目錄下,另外這張圖片最好是一張正方形圖片,因為待會兒我們會它圓形化,然后點擊layout文件夾 — New — Layout resource file,創建一個nav_header.xml文件。修改其中的內容,如下所示:

  可以看到,布局文件的最外層是一個RelativeLayout,我們將它的寬度設為match_parent,高度設為180dp,這是一個NavigationView比較合適的高度,然后指定它的背景色為colorPrimary。

  在RelativeLayout中我們放置了3個控件,CircleImageView是一個用於將圖片圓形化的控件,它的用法非常簡單,基本和ImageView是完全一樣的,這里給它指定了一張圖片作為頭像,然后設置為居中顯示。另外兩個TextView分別用於顯示用戶名和郵箱地址,它們都用到了一些RelativeLayout的定位屬性。

  現在menu和headerLayout都准備好了,我們終於可以使用NavigationView了。修改activity_main.xml中的代碼:

  可以看到,我們將之前TextView換成了NavigationView,這樣滑動菜單中顯示的內容也就變成NavigationView了,這里又通過app:menu和app:headerLayout屬性將我們剛才准備好的menu和headerLayout設置進去,這樣NavigationView就定義完成了。

  NavigationView雖然定義完成了,但是我們還要去處理菜單項的點擊事件才行。修改MainActivity中的代碼:

  代碼還是比較簡單的,這里首先獲取到了NavigationView的實例,然后調用它的setCheckedItem()方法將Call菜單項設置為默認選中。接着調用了setNavigationItemSelectedListener()方法來設置一個菜單項選中事件的監聽器,當用戶點擊了任意菜單項時,就會回調到onNavigationItemSelected()方法中。我們可以在這個方法中寫相應的邏輯處理,不過這里並沒有附加任何邏輯,只是調用了DrawerLayout的closeDrawers()方法將滑動菜單關閉,這也是合情合理的做法。

  現在運行程序,點擊一下Toolbar左側的導航按鈕,效果如圖所示:

  這樣的滑動菜單項頁面看着就好很多了,Material Design的魅力就在這里,它真的是一種非常美觀的設計理念,只要你按照它的各種規范和建議來設計界面,最終做出來的程序就是特別好看的。

五、懸浮按鈕和可交互提示

   立面設計是Material Design中一條非常重要的設計思想,也就是說,按照Material Design的理念,應用程序的界面不僅僅只是一個平面,而應該是有立體效果的。在官方給出的示例中,最簡單最具代表性的立面設計就是懸浮按鈕了,這種按鈕不屬於主界面平面的一部分,而是位於另外一個維度的,因此就會給人一種懸浮的感覺。

  本節中會對這個懸浮按鈕的效果進行學習,另外還會學習一種可交互的提示工具。關於提示工具,我們之前一直都是在使用的Toast,但是Toast只能用於告知用戶某某事情已經發生,用戶不能對此做出任何的響應。

5.1、FloatingActionButton

  FloatActionButton是Design Support庫中提供的一個控件,這個控件可以幫助我們比較輕松地實現懸浮按鈕的效果。其實在之前我們已經預覽過懸浮按鈕是什么樣子的了,它默認會使用colorAccent來作為按鈕的顏色,我們還可以通過給按鈕指定一個圖標來表面這個按鈕的作用是什么。

  接下來具體實現:

第一步:首先准備好一個圖標,這里我放置了一張ic_done.jpg到mipmap-xxhdpi目錄下,然后修改activity_main.xml中的代碼,如下所示:

  這里在主屏幕布局中加入了一個FloatingActionButton。這個控件的用法並沒有什么特別的地方,layout_width和layout_height屬性都指定成wrap_content,layout_gravity屬性指定將這個控件放置於屏幕的右下角,其中end的工作原理和之前的start是一樣的,即如果系統語言是從左往右的,那么end就表示在右邊,如果系統語言是從右往左的,那么end就表示在左邊,然后通過layout_margin屬性給控件的四周留點邊距,緊貼着屏幕邊緣肯定是不好看的,最后通過src屬性給FloatingActionButton設置了一個圖標。

第二步:運行程序,我們看到屏幕右下方出現了一個懸浮按鈕。

  仔細觀察,會發現這個懸浮按鈕的下面還有一點陰影,其實這個很好理解,因為FloatingActionButton是懸浮在當前界面上的,既然是懸浮,那么理所應當會有投影,Design Support庫連這種細節都幫我們考慮到了。

  說到懸浮,其實還可以指定FloatingActionButton的懸浮高度,如下所示:

這里使用app:elevation屬性來給FloatingActionButton指定一個高度值,高度值越大,投影范圍也越大,但是投影效果越淡,高度值越小,投影范圍也越小,但是投影效果越濃。當然這些效果的差異其實都不怎么明顯,默認使用FloatingActionButton效果就已經足夠了。

  接下來看一下FloatingActionButton是如何處理點擊事件的,畢竟,一個按鈕首先要能點擊才有意義,修改MainActivity中的代碼:

  FloatingActionButton其實和普通的Button沒什么兩樣,都是調用setOnClickListener()方法來注冊一個監聽器,當點擊按鈕時,就會執行監聽器中的onClick()方法,這里我們在onClick()方法中彈出了一個Toast。

  重新運行一下程序,點擊FloatingActionButton,效果如下:

5.2、Snackbar

   現在我們已經掌握了FloatingActionButton的基本用法,不過在上一節處理點擊事件的時候,仍然是使用Toast來作為提示工具的,本小節來學習一下Design Support庫提供的更加先進的提示工具——Snackbar。

  首先要明確,Snackbar並不是Toast的替代品,它們兩者之間有着不同的應用場景。Toast的作用是告訴用戶現在發生了什么事情,但同時用戶只能被動接收這個事情,因為沒有什么辦法能讓用戶進行選擇。而Snackbar則在這方面進行了擴展,它允許在提示當中加入一個可交互按鈕,當用戶點擊按鈕的時候可以執行一些額外的邏輯操作,打個比方,如果我們在執行刪除操作的時候只彈出一個Toast提示,那么用戶要是誤刪了某個重要數據的話肯定十分抓狂,但是如果我們增加一個Undo按鈕,就相當於給用戶提供了一個彌補措施,從而大大降低了事故發生的概率,提升了用戶體驗。

  Snackbar的用法也非常簡單,它和Toast基本相似,只不過可以額外增加一個按鈕的點擊事件。修改MainActivity中的代碼:

  可以看到,這里調用了Snackbar的make()方法來創建一個Snackbar對象,make()方法的第一個參數需要傳入一個View,只要是當前界面布局的任意一個View都可以,Snackbar會使用這個View來自動查找最外層的布局,用於展示Snackbar。第二個參數就是Snackbar中顯示的內容,第三個參數是Snackbar顯示的時長,這些都和Toast類似的。

  接着這里又調用了一個setAction()方法來設置一個動作,從而讓Snackbar不僅僅是一個提示,而是可以和用戶進行交互的,簡單起見,我們在動作按鈕的點擊事件里彈出一個Toast提示,最后調用show()方法讓Snackbar顯示出來。

  重新運行程序,並點擊懸浮按鈕,效果如下:

  可以看到,Snackbar從屏幕底部出現了,上面有我們所設置的提示文字,還有一個Undo按鈕,按鈕是可以點擊的,過一段時間后Snackbar會自動從屏幕底部消失。

  不管是出現還是消失,Snackbar都是帶有動畫效果的,因此視覺體驗也會比較好。不過這里有一個bug,這個Snackbar竟然將我們的懸浮按鈕給遮擋住了,雖說也不是什么重大的問題,因為Snackbar過一會兒就會自動消失,但這種用戶體驗總歸是不友好的,需要借助下面的CoordinatorLayout來解決。

5.3、CoordinatorLayout

   CoordinatorLayout可以說是一個加強版的FrameLayout,這個布局也是由Design Support庫提供的。它在普通情況下的作用和FrameLayout基本一致,不過既然是Design Support庫中提供的布局,那么必然有一些Material Design的魔力了。

  事實上,CoordinatorLayout可以監聽其他所有子控件的各種事件,然后自動幫助我們做出最為合理的響應。舉個簡單的例子,剛才彈出的Snackbar提示將懸浮按鈕遮擋住了,而如果我們能讓CoordinatorLayout監聽到Snackbar的彈出事件,那么它會自動將內部的FloatingActionButton向上偏移,從而確保不會被Snackbar遮擋到。

  至於CoordinatorLayout的使用也非常簡單,我們只需要將原來的FrameLayout替換一下就可以了。修改activity_main.xml中的代碼:

  由於CoordinatorLayout本身就是一個加強版的FrameLayout,因此這種替換不會有任何的副作用。現在重新運行程序,並點擊懸浮按鈕,效果如下:

  可以看到,懸浮按鈕自動向上偏移了Snackbar的同等高度,從而確保不會被遮擋住,當Snackbar消失的時候,懸浮按鈕會自動向下偏移回到原來的位置。

  另外懸浮按鈕的向上和向下偏移也是伴隨着動畫效果的,且和Snackbar完全同步,整體效果看上去特別賞心悅目。不過我們回過頭來再思考一下,剛才說的是CoordinatorLayout可以監聽其所有子控件的各種事件,但是Snackbar好像並不是CoordinatorLayout的子控件,為什么它卻可以被監聽到呢?

  其實道理很簡單,還記得在Snackbar的make()方法中傳入的第一個參數嗎?這個參數就是用來指定Snackbar是基於哪個View來觸發的,剛才我們傳入的是FloatingActionButton本身,而FloatingActionButton是CoordinatorLayout中的子控件,因此這個事件就理所應當能被監聽到了。可以自己做個實驗,如果給Snackbar的make()方法傳入一個DrawerLayout,那么Snackbar就會再次遮擋住懸浮按鈕,因為DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout也就無法監聽到Snackbar的彈出和隱藏事件了。

六、卡片式布局

   雖然現在MaterialTest中已經應用了非常多的Material Design效果,不過你會發現,界面上最主要的一塊區域還是處於空白狀態,這塊區域通常都是用來放置應用的主體內容的,這里准備使用一些精美的水果圖片來填充這部分區域。

  那么為了要讓水果圖片也能Material化,本節中我們將會學習如何實現卡片式布局的效果。卡片式布局也是Material Design中提出的一個新的概念,它可以讓頁面中的元素看起來就像在卡片中一樣,並且還能擁有圓角和投影。

6.1、CardView

  CardView是用於實現卡片式布局效果的重要控件,由appcompat-v7庫提供。實際上,CardView也是一個FrameLayout,只是額外提供了圓角和陰影等效果,看上去會有立體的感覺。我們先來看一下CardView的基本用法吧:

  這里定義了一個CardView布局,我們可以通過app:cardCornerRadius屬性指定卡片圓角的弧度,數值越大,圓角的弧度也越大,另外還可以通過app:elevation屬性指定卡片的高度,高度值越大,投影范圍也越大,但是投影效果越淡,高度值越小,投影范圍也越小,但是投影效果越濃,這一點和FloatingActionButton是一致的。

  然后我們在CardView布局中放置了一個TextView,那么這個TextView就會顯示在一張卡片當中了,CardView的用法就是這么簡單。但是我們顯然不可能在如此寬闊的一塊空白區域內只放置一張卡片,為了能夠充分利用屏幕的空間,這里准備綜合運用一下前面的知識,使用RecycleView來填充MaterialTest項目的主界面部分,把之前的那個水果列表進行升級一下,實現一個高配版的水果列表。

  既然要實現水果列表,那么首先是准備水果圖片,將之前的水果圖片復制到mipmap-xxhdpi目錄下。然后由於我們還需要用到RecycleView、CardView這幾個控件,因此必須要在app/build.gradle文件中聲明這些庫的依賴才行:

  注意上述聲明的最后一行,這里添加了一個Glide庫的依賴,Glide是一個超級強大的圖片加載庫,它不僅可以用於加載本地圖片,還可以加載網絡圖片、GIF圖片、甚至是本地視頻,最重要的是,Glide的用法非常簡單,只需要一行代碼就能輕松實現復雜的圖片加載功能,因此這里就准備用它來加載水果圖片。Glide的項目主頁地址是:https://github.com/bumptech/glide。

  接下來開始具體的代碼實現,修改activity_main.xml中的代碼,如下所示:

 

  這里我們在CoordinatorLayout中添加了一個RecyclerView,給它指定了一個id,然后寬度和高度都設置為match_parent,這樣RecyclerView也就占滿了整個布局的空間。

  接着定義一個實體類Fruit,代碼如下所示:

  Fruit類中只有兩個字段,name表示水果的名字,imageId表示水果對應圖片的資源id。

  然后需要為RecyclerView的子項指定一個我們自定義的布局,在layout目錄下新建fruit_item.xml:

  這里使用了CardView來作為子項的最外層布局,從而使得RecyclerView中的每個元素都是在卡片當中的。CardView由於是一個FrameLayout,因此它沒有什么方便的定位方式,這里我們只好在CardView中再嵌套一個LinearLayout,然后在LinearLayout中放置具體的內容。

  內容倒也沒有什么特殊的地方,就是定義了一個ImageView用於顯示水果的圖片,又定義了一個TextView用於顯示水果的名稱,並讓TextView在水平方向上居中顯示,注意在ImageView中我們使用了一個scaleType屬性,這個屬性可以指定圖片的縮放模式。由於各張水果圖片的長寬比例可能都不一致,為了讓所有的圖片都能填充滿整個ImageView,這里使用了centerCrop模式,它可以讓圖片保持原有的比例填充滿ImageView,並將超出屏幕的部分裁剪掉。

  接下來需要為RecyclerView准備一個適配器,新建FruitAdapter類,繼承自RecyclerView.Adapter,並將泛型指定為FruitAdapter.ViewHolder,代碼如下:

   上述代碼已經很熟悉了,和之前編寫的FruitAdapter幾乎一模一樣,唯一需要注意的是,在onBindViewHolder()方法中我們使用了Glide來加載水果的圖片。關於Glide的用法,也沒什么好講的,因為Glide的用法實在是太簡單了。首先調用Glide.with()方法並傳入一個Context、Activity或Fragment參數,然后調用load()方法去加載圖片,可以是一個URL地址,也可以是一個本地路徑,或者是一個資源id,最后調用into()方法將圖片設置到具體某一個ImageView中的就可以了。

  為什么使用Glide而不是傳統的設置圖片方式呢?因為如果圖片的像素都非常高,如果不進行壓縮就直接展示的話,很容易就會引起內存溢出。而使用Glide就完全不需要擔心,因為Glide在內部做了許多非常復制的邏輯操作,其中就包括圖片壓縮,我們只需要安心按照Glide的標准用法去加載圖片就可以了。

  這樣我們就將RecyclerView的適配器也准備好了,最后修改MainActivity中的代碼,如下所示:

代碼分析:

  在MainActivity中我們首先定義了一個數組,數組里面存放了很多個Fruit的實例,每個實例都代表着一種水果,然后再initFruit()方法中,先是清空了一下fruitList中的數據,接着使用一個隨機函數,從剛才定義的Fruit數組中隨機挑選一個水果放入到fruitList當中,這樣每次打開程序看到的水果數據都會是不同的。另外為了讓界面上的數據多一些,這里使用了一個循環,隨機挑選50個水果。

  之后的用法就是RecyclerView的標准用法了,不過這里使用了GridLayoutManager這種布局方式,前面已經學過了LinearLayoutManager和StaggeredGridLayoutManager,現在終於將所以的布局都補齊了,GridLayoutManager的用法也沒有什么特別之處,它的構造函數接收兩個參數,第一個是Context,第二個是列數,這里我們希望每一行中會有兩列數據。

  現在運行程序,效果如下:

 

  可以看到,精美的水果圖片成功的展示出來了,每個水果都是在一張單獨的卡片當中的,並且還擁有圓角和投影,由於是使用隨機的方式來獲取水果數據的,因此界面上會有一些重復的水果出現,這是正常現象。

  但是我們發現,Toolbar不見了,這是被RecyclerView給擋住了,為了解決這個問題,就需要借助下面需要學的另外一個工具了——AppBarLayout。

6.2、AppBarLayout

  首先分析一下為什么RecyclerView會把Toolbar給遮擋住。由於RecyclerView和Toolbar都是放置在CoordinatorLayout中的,而前面說過,CoordinatorLayout就是一個加強版的FrameLayout,那么FrameLayout中的所有控件在不進行明確定位的情況下,默認都會擺放在布局的左上角,從而也就產生了遮擋的現象,其實這已經不是第一次遇到這種情況了,在學習FrameLayout的時候就已經見識過控件與控件之間遮擋的效果。

  那么該如何解決這種問題呢?傳統情況下,使用偏移是唯一的解決辦法,即讓RecyclerView向下偏移一個Toolbar高度,從而保證不會遮擋到Toolbar,不過我們使用的並不是普通的FrameLayout,而是CoordinatorLayout,因此自然會有一些更加巧妙的解決辦法。

  這里准備使用Design Support庫中提供的另外一個工具——AppBarLayout,AppBarLayout實際上是一個垂直方向的LinearLayout,它在內部做了很多滾動事件的封裝,並應用了一些Material Design的設計理念。

  那么我們怎樣使用AppBarLayout才能解決前面的覆蓋問題呢?其實只需要兩步就可以了。

第一步:將Toolbar嵌套到AppBarLayout中。

第二步:給RecyclerView指定一個布局行為。

修改activity_main.xml中的代碼:

  可以看到布局文件並沒有什么大的變化,首先定義了一個AppBarLayout,並將Toolbar放置在了AppBarLayout里面,然后在RecyclerView中使用app:layout_behavior屬性指定了一個布局行為,其中appbar_scrolling_view_behavior這個字符串也是由Design Support庫提供的。

  現在重新運行一下程序,我們看到一切都正常了:

 

  雖說使用AppBarLayout已經成功解決了RecyclerView遮擋Toolbar的問題,但是剛才有提到過,說AppBarLayout中應用了一些Material Design的設計理念,好像從上面的例子中完全沒有體現出來,事實上,當RecyclerView滾動的時候就已經將滾動事件都通知給AppBarLayout了,只是我們還沒有進行處理而已。那么下面就來進一步優化,看看AppBarLayout到底能實現什么樣的Material Design效果。

  當AppBarLayout接收到滾動事件的時候,它的內部子控件其實是可以指定如何去影響這些事件的,通過app:layout_scrollFlags屬性就能實現,修改activity_main.xml中的代碼。如下所示:

  這里在Toolbar中添加了一個app:layout_scrollFlags屬性,並將這個屬性的值指定成了scroll | enterAlways | snap。其中scroll表示當RecyclerView向上滾動的時候,Toolbar會跟着一起向上滾動並實現隱藏;enterAlways表示當RecyclerView向下滾動的時候,Toolbar會跟着一起向下滾動並重新顯示。snap表示當Toolbar還沒有完全隱藏或顯示的時候,會根據當前滾動的距離,自動選擇是隱藏還是顯示。

  只要改動這一行代碼就行了,重新運行程序,並向上滾動RecyclerView,效果如下:

  

  可以看到,隨着我們向上滾動RecyclerView,Toolbar就消失了,而向下滾動RecyclerView,Toolbar又會重新出現。這其實也是Material Design中的一項重要設計思想,因為當用戶在向上滾動RecyclerView的時候,其注意力肯定是在RecyclerView的內容上面,這個時候如果Toolbar還占據這屏幕空間,就會在一定程序上影響用戶的閱讀體驗,而將Toolbar隱藏則可以讓閱讀體驗達到最佳狀態。當用戶需要操作Toolbar上的功能時,只需要輕微向下滾動,Toolbar就會重新出現,這種設計方式,既保證了用戶的最佳閱讀效果,又不影響任何功能上的操作。

  像這種功能如果使用ActionBar的話,那就完全不能實現了,Toolbar的出現為我們提供了更多的可能。

七、下拉刷新

   下拉刷新這種功能幾乎所有的應用里都會有這個功能,不過市面上現有的下拉刷新功能在風格上各有不同,並且和Material Design還有些格格不入的感覺。因此,谷歌為了讓Android的下拉刷新風格有一個統一的標准,於是在Material Design中制定了一個官方的設計規范,當然,我們並不需要去深入了解這個規范到底是什么樣的,因為谷歌早就提供好了現成的控件,我們只需要在項目中直接使用就可以了。

  SwipeRefreshLayout就是用於實現下拉刷新功能的核心類,它是由support-v4庫提供的。我們把想要實現下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速讓這個控件支持下拉刷新。那么在MaterialTest項目中,應該支持下拉刷新功能的控件自然就是RecyclerView了。

  由於SwipeRefreshLayout的用法也比較簡單,下面我們就直接開始使用了。修改activity_main.xml中的代碼,如下所示:

  可以看到,這里我們在RecyclerView的外面又嵌套了一層SwipeRefreshLayout,這樣RecyclerView就自動擁有下拉刷新功能了。另外需要注意,由於RecyclerView現在變成了SwipeRefreshLayout的子控件,因此之前使用app:layout_behavior聲明的布局行為現在也移動到SwipeRefreshLayout中才行。

  不過這還沒結束,雖然RecyclerView已經支持下拉刷新功能了,但是我們還要在代碼中處理具體的刷新邏輯才行。修改MainActivity中的代碼:

  這段代碼應該還是比較好理解,首先通過findViewById()方法來拿到SwipeRefreshLayout的實例,然后調用setColorSchemeResources()方法來設置下拉刷新進度條的顏色,這里我們就使用主題中的colorPrimary作為進度條的顏色,接着調用setOnRefreshListener()方法來設置一個下拉刷新的監聽器,當觸發了下拉刷新操作的時候就會回調這個監聽器的onRefresh()方法,然后我們在這里去處理具體的刷新邏輯就可以了。

  通常情況下,onRefresh()方法中應該是去網絡上請求最新的數據,然后再將這些數據顯示出來,這里簡單起見,我們就不和網絡進行交互了,而是調用一個refreshFruits()方法進行本地刷新操作。refreshFruits()方法中先是開啟了一個線程,然后將線程沉睡兩秒鍾。之所以這么做,是因為本地刷新操作速度非常快,如果不將線程沉睡的話,刷新立刻就結束了,從而看不到刷新的過程。沉睡結束后,這里使用了runOnUiThread()方法將線程切換回主線程,然后調用initFruit()方法重新生成數據,接着再調用FruitAdapter的notifyDataSetChanged()方法通知數據發生了變化,最后調用SwipeRefreshLayout的setRefreshing()方法並傳入false,用於表示刷新事件結束,並隱藏刷新進度條。

  重新運行程序,在屏幕的主界面向下拖動,會有一個下拉刷新的進度條出現,松手后就會自動進行刷新了,效果如下:

  下拉刷新的進度條只會停留兩秒鍾,之后就會自動消失,界面上的水果數據也會隨之更新。

  這樣我們就把下拉刷新的功能也成功實現了,並且這就是Material Design中規定的最標准的下拉刷新效果。目前我們的項目中已經應用了眾多Material Design的效果,Design Support庫中的常用控件也學了大半,接下來還要學一個非常震撼的Material Design效果——可折疊式標題欄。

八、可折疊式標題欄

   雖說我們現在的標題欄是使用Toolbar來編寫的,但是它看上去和傳統的ActionBar其實沒有什么兩樣,只不過可以響應RecyclerView的滾動事件來進行隱藏和顯示,而Material Design中並沒有限定標題欄必須是長什么樣子的,事實上,我們可以根據自己的喜好隨意定制標題欄的樣式,這一節我們實現一個可折疊式標題欄的效果,需要借助於CollapsingToolbarLayout這個工具。

8.1、CollapsingToolbarLayout

   顧名思義,CollapsingToolbarLayout是一個作用於Toolbar基礎之上的布局,它也是由Design Support庫提供的。CollapsingToolbarLayout可以讓Toolbar的效果變得更加豐富,不僅僅是展示一個標題欄,而是能夠實現非常華麗的效果。

  不過,CollapsingToolbarLayout是不能獨立存在的,它在設計的時候就被限定只能作為AppBarLayout的直接子布局來使用。而AppBarLayout又必須是CoordinatorLayout的子布局,因此本節中我們要實現的功能其實需要綜合運用前面所學的各種的知識。

第一步:首先我們需要一個額外的活動來作為水果的詳情展示界面,新建一個Empty Activity,命名為FruitActivity,並將布局名指定為activity_fruit.xml,然后我們開始編寫水果詳情展示界面的布局。由於整個布局文件比較復雜,這里准備采用分段編寫的方式,activity_fruit.xml中的內容主要分為兩部分,一個是水果標題欄,一個是水果內容詳情,現在一步一步實現。

  首先實現標題欄部分,這里使用CoordinatorLayout來作為最外層布局,如下所示:

  一開始的代碼還是比較簡單的,相信沒有什么需要解釋的地方。注意始終記得要定義一個xmlns:app的命名空間,在Material Design的開發中會經常用到它。

  接着我們在CoordinatorLayout中嵌套一個AppBarLayout,如下所示:

  目前為止也沒有什么難理解的地方,我們給AppBarLayout定義了一個id,將它的高度指定為match_parent,高度指定為250dp。當然這里的高度值可以隨意指定。

  接下來在AppBarLayout中再嵌套一個CollapsingToolbarLayout,如下所示:

  從現在開始就有點難理解了,這里我們使用了新的布局CollapsingToolbarLayout,其中,id、layout_width和layout_height這幾個屬性比較簡單。

  android:theme屬性指定了一個ThemeOverlay.AppCompat.Dark.ActionBar的主題,其實對於這部分也並不陌生,因為之前在activity_main.xml中給Toolbar指定的也是這個主題,只不過這里實現更加高級的Toolbar效果,因此需要將這個主題的指定提到上一層來。

  app:contentScrim屬性用於指定CollapsingToolbarLayout在趨於折疊狀態以及折疊之后的背景色,其實CollapsingToolbarLayout在折疊之后就是一個普通的Toolbar,那么背景色肯定應該是colorPrimary了,具體的效果待會就能看到。

  app:layout_scrollFlags屬性也見過,只不過之前是Toolbar指定的,現在也移到外面來了,其中,scroll表示CollapsingToolbarLayout會隨着水果內容詳情的滾動一起滾動,exitUntilCollapsed表示當CollapsingToolbarLayout隨着滾動完成折疊之后就保留在界面上,不再移出屏幕。

  接下來在CollapsingToolbarLayout中定義標題欄的具體內容,如下所示:

  可以看到,在CollapsingToolbarLayout中定義了一個ImageView和一個Toolbar,也就意味着,這個高級版的標題欄將是由普通的標題欄上圖片組合而成的,這里定義的大多數屬性都是見過的,只有一個app:layout_collapseMode比較陌生,它用於指定當前控件在CollapsingToolbarLayout折疊過程中的折疊模式,其中Toolbar指定成pin,表示在折疊的過程中位置始終保持不變,ImageView指定成parallax,表示會在折疊的過程中產生一定的錯誤偏移,這種模式的視覺效果會非常好。

  這樣我們就將水果的標題欄的界面編寫好了。

第二步:接下來開始編寫水果內容詳情部分,繼續修改activity_fruit.xml中的代碼:

  水果內容詳情的最外層布局使用了一個NestedScrollView,注意它和AppBarLayout是平級的。之前學過ScrollView的用法,它允許使用滾動的方式來查看屏幕以外的數據,而NestedScrollView在此基礎上還增加了嵌套響應滾動事件的功能。由於CoordinatorLayout本身已經可以響應滾動事件了,因此我們在它的內部就需要使用NestedScrollView或RecyclerView這樣的布局,另外,這里還通過app:layout_behavior屬性指定了一個布局行為,這和之前在RecyclerView中的用法是一模一樣的。

  不管是ScrollView還是NestedScrollView,它們的內部都只允許存在一個直接子布局,因此,如果我們想要在里面放入很多東西的話,通常都會先嵌套一個LinearLayout,然后再在LinearLayout中放入具體的內容就可以了,如下所示:

  這里我們嵌套了一個垂直方向的LinearLayout,並將layout_width設置為match_parent,將layout_height設置為wrap_content。

  接下來在LinearLayout中放入具體的內容,這里我准備使用一個TextView來顯示水果的內容詳情。並將TextView放在一個卡片式布局當中,如下所示:

  這段代碼也沒有什么難理解的地方,都是學過的知識,需要注意的是,這里為了讓界面更加美觀,我在CardView和TextView上都加上了一些邊距,其中,CardView的marginTop加了35dp的邊距,這是為下面要編寫的東西留出空間。

  現在就把水果標題欄和水果內容詳情的界面都編寫完了,不過我們還可以在界面上再添加一個懸浮按鈕,這個懸浮按鈕並不是必需的,根據具體的需求添加就可以了,如果加入的話,將免費獲得一些額外的動畫效果。

  為了示范,准備在activity_fruit.xml中加入一個懸浮按鈕,這個界面是一個水果詳情展示界面,那么這里就加入一個表示評論作用的懸浮按鈕吧。首先需要提前准備好一個圖標,這里放置了一張ic_comment.jpg到mipmap-xxhdpi目錄下,然后修改activity_fruit.xml中的代碼,如下所示:

  可以看到,這里加入了一個FloatingActionButton,它和AppBarLayout以及NestedScrollView是平級的。FloatingActionButton中使用app:layout_anchor屬性指定了一個錨點,我們錨點設置為AppBarLayout,這樣懸浮按鈕就會出現在水果標題欄的區域內,接着又使用app:layout_anchorGravity屬性將懸浮按鈕定位在標題欄區域的右下角,其他一些屬性都比較簡單,就不多說了。

  到此為止,我們已經將整個activity_fruit.xml布局都編寫完了,內容雖然比較長,但由於是分段編寫的,並且每一步都進行了詳細的說明。

第三步:界面完成之后,接下來我們開始編寫功能邏輯,修改FruitActivity中的代碼,如下所示:

代碼分析:

  FruitActivity中的代碼並不是很復雜。首先在onCreate()方法中,我們通過Intent獲取到傳入的水果名和水果圖片的資源id,然后通過findViewById()方法拿到剛才在布局文件中定義的各個控件的實例,接着就是使用Toolbar的標准用法,將它作為ActionBar顯示,並啟用HomeAsUp按鈕,由於HomeAsUp按鈕的默認圖標就是一個返回箭頭,這正是我們所期望的,因此就不用再額外設置別的圖標了。

  接下來開始填充界面上的內容,調用CollapsingToolbarLayout的setTitle()方法將水果名設置成當前界面的標題,然后使用Glide加載傳入的水果圖片,並設置到標題欄的ImageView上面。接着需要填充水果的內容詳情,由於這只是一個示例程序,並不需要什么真實的數據,所以這里使用了一個generateFruitContent()方法將水果名循環拼接500次,從而生成一個比較長的字符串,將它設置到了TextView上面。

  最后,在onOptionsItemSelected()方法中處理了HomeAsUp按鈕的點擊事件,當點擊了這個按鈕時,就調用finish()方法關閉當前的活動,從而返回上一個活動。

第四步:處理RecyclerView的點擊事件,修改FruitAdapter中的代碼:

  這是最關鍵的一步,這里給CardView注冊了一個點擊事件監聽器,然后在點擊事件獲取當前點擊項的水果名和水果圖片資源id,把它們傳入到Intent中,最后調用startActivity()方法啟動FruitActivity。

第五步:運行程序,並點擊界面上的任意一個水果,效果如下:

  這個界面上的內容分為三部分,水果標題欄、水果內容詳情和懸浮按鈕。Toolbar和水果背景圖完美地融合到了一起,既保證了圖片的展示空間,又不影響Toolbar的任何功能。

  不過這並不是全部,嘗試向上拖動水果內容詳情,會發現背景圖上的標題會慢慢縮小,並且背景圖會產生一些錯位偏移的效果。這是由於用戶想要查看水果的內容詳情,此時界面的重點在具體的內容上面,因此標題欄就會自動進行折疊,從而節省屏幕空間。繼續向上拖動,直到標題欄變成完全折疊狀態,如下所示,可以看到標題欄的背景圖片不見了,懸浮按鈕也自動消失了,現在的水果標題欄變成了一個普通的Toolbar,這是由於用戶正在閱讀具體的內容,需要給他們提供最充分的閱讀空間。而如果這個時候向下拖動水果內容詳情,就會執行一個完全相反的動畫過程,最終恢復原樣。

 

8.2、充分利用系統狀態欄空間

  雖說現在水果詳情展示界面的效果已經非常華麗了,但是這並不代表不能再進一步地提升,如果將背景圖片和狀態欄融合到一起,那視覺體驗絕對會提升好多檔次。只不過可惜的是,在Android5.0之前,我們是無法對狀態欄的背景或顏色進行操作的,那個時候也還沒有Material Design的概念。但是在Android5.0之后的系統都是支持這個功能的,因此這里就來實現一個系統差異型的效果,在Android5.0之后的系統中,使用背景圖和狀態欄融合的模式,在之前的系統中使用普通的模式。

  想要讓背景圖能夠和系統狀態欄融合,需要借助android:fitsSystemWindows這個屬性來實現。在CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout這種嵌套結構的布局中中,將看見的android:fitsSystemWindows屬性指定成true,就表示該控件會出現在系統狀態欄里,對應到我們的程序,那就是水果標題欄中的ImageView應該設置這個屬性了。不過只給ImageView設置這個屬性是沒有用的,我們必須將ImageView布局結構中的所有父布局都設置上這個屬性才可以,修改activity_fruit.xml中的代碼,如下所示:

  但是,即使我們將android:fitsSystemWindows屬性都設置好了還是沒有用的,因為必須在程序的主題中將狀態欄顏色指定成透明色才行。指定成透明色的方法很簡單,在主題中將android:statusBarColor屬性的值指定成@android:color/transparent就可以了,但問題在於,android:statusBarColor這個屬性是從API21,也就是Android5.0系統開始有的,之前的系統無法指定這個屬性,那么系統差異型的功能實現就要從這里開始了。

  右擊res目錄 —> New —> Directory,創建一個values-v21目錄,然后右擊values-v21目錄 —> New —> Values resource file,創建一個styles.xml文件,接着對這個文件進行編寫,代碼如下:

  這里我們定義了一個FruitActivityTheme主題,它是專門給FruitActivity使用的。FruitActivityTheme的parent主題是AppTheme,也就是說,它繼承了AppTheme中的所有特性。然后我們在FruitActivityTheme中將狀態欄的顏色指定成透明色,由於values-v21目錄是只有Android5.0及以上的系統才會去讀取的,因此這么聲明是沒有問題的。

  但是Android5.0之前的系統卻無法識別FruitActivityTheme這個主題,因此我們還需要對Values/styles/xml文件進行修改,如下所示:

  可以看到,這里也定義了一個FruitActivityTheme主題,並且parent主題也是AppTheme,但是它的內部是空的,因為Android5.0之前的系統無法指定狀態欄的顏色,因此這里什么都不用做就可以了。

  最后我們還需要讓FruitActivity使用這個主題才可以,修改AndroidManifest.xml中的代碼,如下所示:

  這里使用了android:theme屬性單獨給FruitActivity指定了FruitActivityTheme這個主題,這樣就大功告成了。現在只有運行在Android5.0以上的系統運行MaterialTest程序,水果詳情展示界面的效果就會如圖所示:

 

九、總結

  本節的知識點比較多,充分利用了Design Support庫、support-v4庫、appcompat-v7庫,以及一些開源項目來實現了一個高度Material化的應用程序,能將這些庫中的相關控件熟練掌握。實際上,Material Design的設計思維和設計理念才是更加重要的東西,可以參考Material Design的官方文章:http://design.1sters.com/.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM