Material Design 實戰 之 第六彈 —— 可摺疊式標題欄(CollapsingToolbarLayout) & 系統差異型的功能...
本模組共有六篇文章,參考郭神的《第一行程式碼》,對Material Design的學習做一個詳細的筆記,大家可以一起交流一下:
- ofollow,noindex">Material Design 實戰 之第一彈——Toolbar(即本文)
- Material Design 實戰 之第二彈——滑動選單詳解&實戰
- Material Design 實戰 之第三彈—— 懸浮按鈕和可互動提示(FloatingActionButton & Snackbar & CoordinatorLayout)
- Material Design 實戰 之第四彈 —— 卡片佈局以及靈動的標題欄(CardView & AppBarLayout)
- Material Design 實戰 之第五彈 —— 下拉重新整理(SwipeRefreshLayout)
- Material Design 實戰 之 第六彈 —— 可摺疊式標題欄(CollapsingToolbarLayout) & 系統差異型的功能實現(充分利用系統狀態列空間)
引子:

文章提要與總結
1. CollapsingToolbarLayout 1.1 CollapsingToolbarLayout是一個作用於Toolbar基礎之上的佈局,由DesignSupport庫提供。 1.2 CollapsingToolbarLayout不能獨立存在, 它在設計的時候就被限定只能作為AppBarLayout的直接子佈局來使用。 而AppBarLayout又必須是CoordinatorLayout的子佈局;
1.3 水果詳情介面佈局框架: CoordinatorLayout下分三部分:水果標題欄、水果詳情欄、懸浮按鈕; 具體屬性意義詳見文章; <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout...... android:fitsSystemWindows="true"> <!--水果標題欄--> <android.support.design.widget.AppBarLayout...... android:layout_height="250dp" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout...... android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView...... android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar...... app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <!--水果詳情欄--> <android.support.v4.widget.NestedScrollView...... app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout ......> <android.support.v7.widget.CardView...... android:layout_marginBottom="15dp"...... app:cardCornerRadius="4dp"> <TextView...... android:layout_margin="10dp"/> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton...... android:src="@drawable/ic_comment" app:layout_anchor="@id/appBar" app:layout_anchorGravity="bottom|end"/> </android.support.design.widget.CoordinatorLayout>
1.4 水果詳情介面java邏輯: public class FruitActivity extends AppCompatActivity { 全域性變數:設定約定 intent 傳輸鍵值常量; @Override protected void onCreate(Bundle savedInstanceState){ 此類是水果詳情頁,通過intent獲得來自水果卡片列表頁傳來的資料(水果名字和圖片id); 例項化諸物件; 設定toolbar 設定導航按鈕 設定摺疊欄標題 載入圖片,設定文字 } 利用StringBuilder重複fruitname生成長字串 private String generateFruitContent(String fruitName){ } 響應導航按鈕 @Override public boolean onOptionsItemSelected(MenuItem item) {} } 1.5 處理RecyclerView的點選事件,將點選到的卡片子項提取出name和imageId, 用intent傳給水果詳情介面展示;
2. 充分利用系統狀態列空間(系統差異型) 2.1 將控制元件(這裡是ImageView)佈局結構中的所有父佈局的 android:fitsSystemWindows屬性指定成true,就表示該控制元件會出現在系統狀態列裡; 2.2 在程式的主題中將狀態列顏色指定成透明色; 在主題中將android:statusBarColor屬性的值指定成@android:color/transparent;
2.3 建立一個values-v21目錄;values-v21目錄下建立一個styles.xml檔案; 編寫: <?xml version="1.0" encoding="utf-8"?> <resources> <style name="FruitActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources> 2.4 修改values/styles.xml檔案: <resources>...... <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">...... </style> <style name="FruitActivityTheme" parent="AppTheme"> </style> </resources> 2.5 修改AndroidManifest.xmI: <activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"> </activity>
- 關於的 Material Design 學習到此就告一段落了,具體的可以參考 Material Design的官方文件;
正文
可摺疊式標題欄(CollapsingToolbarLayout)
顧名思義,CollapsingToolbarLayout是一個作用於Toolbar基礎之上的佈局,由DesignSupport庫提供。
CollapsingToolbarLayout可以讓Toolbar的效果變得更加豐富,不僅僅是展示一個標題欄,而是能夠實現非常華麗的效果。
不過CollapsingToolbarLayout不能獨立存在,它在設計的時候就被限定只能作為AppBarLayout的直接子佈局來使用。而AppBarLayout又必須是CoordinatorLayout的子佈局。
本文來做一個額外的活動作為水果的詳情展示介面,當點選水果列表卡片的時候就進入這個介面。
右擊com.example.materialtest包—>New—>Activity—>EmptyActivity,建立一個FruitActivity,並將佈局名指定成 activity_fruit.xml
。
接著我們來編寫這個佈局。
Activity_fruit.xml中的內容主要分為兩部分,一個是水果標題欄,一個是水果內容詳情。
首先實現標題欄部分,這裡使用CoordinatorLayout來作為最外層佈局(我們在講監測snackbar彈出,解決其遮擋懸浮按鈕問題的時候用到過這個佈局),如下:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.design.widget.CoordinatorLayout>
接著在裡面巢狀一個AppBarLayout:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="250dp"> </android.support.design.widget.AppBarLayout> </android.support.design.widget.CoordinatorLayout>
這裡主要是將高度指定為250dp,郭神親測覺得這樣視覺效果比較好。
接著在AppBarLayout中再巢狀一個CollapsingToolbarLayout:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="250dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> </android.support.design.widget.CoordinatorLayout>
這裡使用了新的佈局CollapsingToolbarLayout。
其中,
-
android:theme指定了ThemeOverlay.AppCompat.Dark.ActionBar主題,之前(第四彈)在activitymain.xml中給Toolbar指定的也是這個主題,只不過這裡要實現更加高階的Toolbar效果,因此需要將這個主題的指定提到上一層來。
-
app:contentScrim指定CollapsmgToolbarLayout在趨於摺疊狀態以及摺疊之後的背景色,
其實CollapsingToolbarLayout在摺疊之後就是一個普通的Toolbar,背景色是colorPrimary; -
app:layout_scrollFlags之前(第四彈)是給Toolbar指定的,現在也移到外面來了。
其中,
- scroll表示CollapsingToolbarLayout會隨著水果內容詳情的滾動一起滾動,
- exitUntilCollapsed表示當CollapsingToolbarLayout隨著滾動完成摺疊之後就保留在介面上,不再移出螢幕。
接下來在CollapsingToolbarLayout中定義標題欄的具體內容:
...... <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/fruit_image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> ......
這裡在CollapsingToolbarLayout中定義了一個ImageView和一個Toolbar,也即這個高階版的標題欄是由普通的標題欄加上圖片組合而成的。
以及,
-
app:layout_collapseMode用於指定當前控制元件在CollapsingToolbarLayout摺疊過程中的摺疊模式,
其中Toolbar指定成pin,表示在摺疊的過程中位置始終保持不變,
- ImageView指定成parallax,表示會在摺疊的過程中產生一定的錯位偏移,這種模式的視覺效果會非常好。
下面編寫水果內容詳情部分,繼續改activity_fruit.xml:
...... </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
我們知道ScrollView允許使用滾動的方式來檢視螢幕以外的資料,
而NestedScrollView在此基礎之上增加了巢狀響應滾動事件的功能。
由於CoordinatorLayout本身已經可以響應滾動事件了,
因此我們在它的內部就需要使用NestedScrollView或RecyclerView這樣可以響應滾動事件的佈局。
另外,通過
app:layout_behavior屬性指定一個佈局行為,這和之前第四彈
在RecyclerView中的用法是一模一樣的。
不管是ScrollView還是NestedScroIIView,它們的內部都只允許存在一個直接子佈局。
如果我們想要在裡面放入很多東西的話,通常都會先巢狀一個LinearLayout,然後再在LinearLayout中放入具體的內容,如下:
...... <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
接下來在LinearLayout中放入具體的內容,
使用一個TextView來顯示水果的內容詳情,
並將TextView放在一個卡片式佈局當中:
...... </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="15dp" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:layout_marginTop="35dp" app:cardCornerRadius="4dp"> <TextView android:id="@+id/fruit_content_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp"/> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
這裡主要要注意的是,為了讓介面更加美觀,在CardView和TextView上都加了一些邊距。
其中,
CardView的marginTop加了35dp的邊距,
這是為下面要編寫的東西留出空間。
至此水果標題欄和水果內容詳情的介面便編寫完了。
接著還可以在介面上再新增一個懸浮按鈕,
當然並不是必需的,只是如果加的話,我們將免費獲得一些額外的動畫效果。
這裡就實戰一下,在activity_fruit.xml中加一個關於水果的表示評論作用的懸浮按鈕。
首先需要提前準備好一個圖示,
這裡放置了一張ic_comment.png到drawable-xxhdpi目錄下。
然後修改activity_fruit.xml:
...... </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@drawable/ic_comment" app:layout_anchor="@id/appBar" app:layout_anchorGravity="bottom|end"/> </android.support.design.widget.CoordinatorLayout>
這裡加了一個FloatingActionButton,它和 AppBarLayout(水果標題欄)
以及 NestedScrollView(水果詳情欄)
佈局平級。
FloatingActionButton中,
app:layou_anchor
屬性(anchor n.錨狀物)指定了一個錨點,這裡將錨點設定為AppBarLayout,這樣懸浮按鈕就會出現在水果標題欄的區域內;
app:layout_anchorGravity
屬性將懸浮按鈕定位在標題欄區域的右下角。
至此activity_fruit.xml佈局(水果詳情介面)便寫完了。
介面完成了之後,接著開始編寫功能邏輯,修改FruitActivity(水果詳情介面的邏輯):


public class FruitActivity extends AppCompatActivity { //設定約定 intent 傳輸鍵值常量 public static final String FRUIT_NAME = "fruit_name"; public static final String FRUIT_IMAGE_ID = "fruit_image_id"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fruit); //此類是水果詳情頁,通過intent獲得來自水果卡片列表頁傳來的資料(水果名字和圖片id) Intent intent = getIntent(); String fruitname = intent.getStringExtra(FRUIT_NAME); int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0); //例項化諸物件 Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view); TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text); //設定toolbar setSupportActionBar(toolbar); //設定導航按鈕 ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true); } //設定摺疊欄標題 collapsingToolbar.setTitle(fruitname); //載入圖片,設定文字 Glide.with(this).load(fruitImageId).into(fruitImageView); String fruitContent = generateFruitContent(fruitname); fruitContentText.setText(fruitContent); } //利用StringBuilder重複fruitname生成長字串 private String generateFruitContent(String fruitName){ StringBuilder fruitContent = new StringBuilder(); for(int i = 0; i < 500; i++){ fruitContent.append(fruitName); } return fruitContent.toString(); } //響應導航按鈕 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); } }
接下來處理RecyclerView的點選事件,將點選到的卡片子項提取出name和imageId,
用intent傳給水果詳情介面展示,下面修改FruitAdapter:
...... //載入子佈局,將子項作為引數傳給ViewHolder,在ViewHolder裡面 //為cardView新增點選事件 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mContext == null){ mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); final ViewHolder holder = new ViewHolder(view);//將子項作為引數傳給ViewHolder,在ViewHolder裡面面例項化子項中的各個物件 holder.cardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition();//獲得列表對應子項的位置 //mFruitList由new介面卡的時候傳進來的建構函式的引數提供, // get(position)將子項對應位置的水果物件從設定到介面卡中的水果列表資料中取出來 Fruit fruit = mFruitList.get(position); Intent intent = new Intent(mContext, FruitActivity.class); intent.putExtra(FruitActivity.FRUIT_NAME, fruit.getName()); intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId()); mContext.startActivity(intent); } }); return holder; } ......
修改前(原全文見第四彈):
...... //載入子佈局,將子項作為引數傳給ViewHolder,在ViewHolder裡面 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mContext == null){ mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); return new ViewHolder(view);//將子項作為引數傳給ViewHolder,在ViewHolder裡面例項化子項中的各個物件 } ......
重新執行一下程式,效果如下:

向上拖動水果圖片,背景圖上的標題會慢慢縮小,而且會產生錯位偏移的效果,toolbar的位置和圖片還會產生透明度的變化等等,效果十分炫酷。


這是由於使用者想要檢視水果的內容詳情,此時介面的重點在具體的內容上面,因此標題欄就會自動進行摺疊,從而節省螢幕空間。
繼續向上拖動,直到標題欄變成完全摺疊狀態,效果如圖:

那個這裡的話其實有個小尷尬,再次強調注意命名規範的重要性了。。。
首先這個是 水果詳情介面標題欄
的ImageView,id是 fruit_image_view
:

另下面這個是 卡片水果列表介面
的ImageView,id是 fruit_image
:

我在水果詳情介面的邏輯中,findViewbyid寫錯成了 卡片水果列表介面
的ImageView的id:

java.lang.IllegalArgumentException: You must pass in a non null View
畢竟點選水果卡片之後是要跳轉到水果詳情介面了,邏輯還要去設定進入onStop()狀態的 卡片水果列表介面
的控制元件,顯然這肯定是不行的。
將剛剛寫錯的地方——例項化使用的id改正一下即可:
ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view);
充分利用系統狀態列空間

。
只不過Android5.0系統之前是無法對狀態列的背景或顏色進行操作的,那個時候也沒有Matenal 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:statusBarCoIor這個屬性是從API 21,即Android5.0系統開始才有的,之前的系統無法指定這個屬性。
那麼,系統差異型的功能實現至此開始;
右擊res目錄—>New—>Directory,建立一個values-v21目錄,然後右擊values-v21目錄—>New
—>Values resource file,建立一個styles.xml檔案。對這個檔案進行編寫:
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="FruitActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources>
這裡定義了一個FruitActivityTheme主題,
- 它是專門給FruitAcuvity使用的。
- FruitActivityTheme的parent主題是AppTheme,也就是說它繼承了AppTheme中的所有特性。
-
然後在FruitAcuvityTheme中將狀態列的顏色指定成透明色,
由於values-v21目錄是隻有Android5.0及以上的系統才會去讀取的,
因此這麼宣告是沒有問題的。
但是Android5.0之前的系統卻無法識別FruitActivityTheme這個主題,因此還需修改values/styles.xml檔案:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <!--修改了這兒--> <style name="FruitActivityTheme" parent="AppTheme"> </style> </resources>
這裡也定義了一個FruitActivityTheme主題,並且parent主題也是AppTheme,但是它的內部是空的。
因為Android5.0之前的系統無法指定狀態列的顏色,因此這裡什麼都不用做就可以了。
5.0之前的版本會載入這裡的FruitActivityTheme,也就是間接地使用了預設的AppTheme主題;
5.0之後的版本或許也載入這裡的FruitActivityTheme,但同時讀取values-v21的styles,隨後剛剛我們做的設定狀態列的程式碼會將這裡的覆蓋掉,也就是使用了我們編寫的新的FruitActivityTheme。
於是達到了 系統差異型的功能實現
的目的。
最後還需讓FruitActivity使用這個主題,修改AndroidManifest.xmI:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.materialtest"> <application ...... <activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"> </activity> </application> </manifest>
這裡使用android:theme屬性單獨給FruitActivity指定了這個主題,到這裡就大功告成了。
現在只要是在Android5.0及以上的系統執行這個MaterialTest程式,水果詳情展示介面的效果便如下:

跟剛剛的效果相比,視覺體驗是完全不同檔次的。

關於的 Material Design 學習到此就告一段落了,具體的可以參考 Material Design的官方文件 :
