網易雲音樂App給用戶的體驗效果一直都很是好,尤爲是流暢的動畫和滑動的聯動效果,都給人一種如絲滑般的感覺,這一點在其歌手詳情頁面體現得尤其突出。那麼咱們就來實現這樣的效果,可是咱們不能只侷限在實現當中,不然當需求變化就須要改動大量的代碼,同時也不能保證它的複用性,放到其餘界面則須要寫許多重複代碼。所以咱們須要跳出實現的限制,將其中的元素抽取出來,製做成一個通用的庫,而且保證其可拓展性和充分的用戶自定義性。通過研究,最終實現了此控件,並取名爲HeaderLayout,那麼咱們先來看看實現效果以便直觀的感覺一下。java
效果圖中全部的頭部控件滑動聯動效果都只須要在xml中配置幾行代碼便可完成,因爲HeaderLayout是根據CoordinatorLayout的機制來實現的,因此HeaderLayout須要包裹在CoordinatorLayout中才會有效果。android
implementation "com.imurluck:headerlayout:$lastVersion"
複製代碼
編寫佈局
HeaderLayout繼承自FrameLayout,且並無改寫FrameLayout的測量和佈局邏輯,因此子控件的佈局方式和FrameLayout相同便可,咱們只須要關注HeaderLayout新增的幾個屬性。這裏以效果圖爲例。github
<androidx.coordinatorlayout.widget.CoordinatorLayout ...>
<com.zzx.headerlayout_kotlin.HeaderLayout android:layout_width="match_parent" android:layout_height="wrap_content" //新增屬性 app:extend_height="30%">
<androidx.appcompat.widget.AppCompatImageView android:layout_width="match_parent" android:layout_height="300dp" android:src="@drawable/singer" android:scaleType="centerCrop" //新增屬性 app:transformation="scroll|extend_scale" />
...
</com.zzx.headerlayout_kotlin.HeaderLayout>
<androidx.viewpager.widget.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" //配置依賴佈局的layout_behavior app:layout_behavior="@string/header_layout_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
複製代碼
如上所示,HeaderLayout工做在CoordinatorLayout中而且是其直接子View。ViewPager因爲須要根據HeaderLayout的滑動作出界面的調整,因此須要配置layout_behavior,而且其值爲@string/header_layout_scrolling_view_behavior,這裏和AppBarLayout的使用方式一致。咱們的工做重點是頭部控件的聯動效果,所以我們聚焦於HeaderLayout和其子View。咱們看AppCompatImageView,它用來展現效果圖中的歌手。仔細分析效果圖中AppCompatImageView的變換方式,能夠發現它是根據父控件HeaderLayout的滑動而作出的相應的變化效果,HeaderLayout向上滑動,其跟隨向上,HeaderLayout向下滑動,則跟着向下。而且,在HeaderLayout滑動到底部繼續向下拓展時,AppCompatImageView作了一個收縮的變換。這一切的一切都須要歸功於app:transformation屬性,能夠在代碼中看見其值爲"scroll|extend_scale",那麼其含義是什麼呢?對此,咱們引出了一個概念----Transformation,它是一個接口,其意在爲根據HeaderLayout的滑動及狀態而作出相應的變化行爲。在介紹Transformation以前,有必要介紹一下HeaderLayout滑動中的幾種狀態。 app
HeaderLayout的滑動其實是HeaderLayout高度的動態變化,因此須要瞭解圖中三種高度的含義。maxHeight是HeaderLayout第一次加載測量後的高度,minHeight是設置了app:sticky_until_exit="true"屬性的子View的高度之和,此屬性表示子View不隨着HeaderLayout而滑出屏幕,造成一種粘連在屏幕頂部的效果,且子View是按照順序排列的。extendHeight則是拓展的高度,展現在效果圖中就是圖片收縮scale時下滑的高度,extendHeight能夠在xml中爲HeaderLayout設置,其值能夠爲dimension,百分數,或者float比例,百分數和float比例是按照maxHeight而計算的。 而圖中五種狀態用來表示HeaderLayout高度變化過程當中的滑動狀態,Transformation就是根據這五種狀態而生,Transformation做用於HeaderLayout的直接子View或者間接子View(間接子View須要本身進行處理,能夠參考CommonToolbarTransformation),一個子View能夠同時擁有多個Transformation,HeaderLayout在其狀態變化時,則會遍歷子View的全部Transformation,通知其作出改變。佈局
XML中做用於AppCompatImageView的app:transformation="scroll|extend_scale"屬性,scroll 和 extend_scale則是內置的兩種Transformation,以下表所示。學習
屬性 | 值 | 說明 | 做用對象 |
transformation | |||
---|---|---|---|
scroll | 隨着HeaderLayout滑動而滑動 | HeaderLayout直接子View | |
alpha | STATE_MIN_HEIGHT到STATE_MAX_HEIGHT對應alpha爲0->1 | HeaderLayout直接子View | |
alpha_contray | 與alpha相反,STATE_HEIGHT到STATE_MAX_HEIGHT對應alpha爲1->0 | HeaderLayout直接子View | |
extendScale | 在STATE_MAX_HEIGHT到STATE_EXTEND_MAX_END之間作scale變換 | HeaderLayout直接子View | |
common_toolbar | 專爲Toolbar設計,在STATE_MIN_HEIGHT時顯示Title和Subtitle,不然隱藏,此屬性必須設置給HeaderLayout的直接子View,可是Toolbar不須要爲其直接子View | HeaderLayout直接子View | |
sticky_until_exit | true|false | 子View不隨HeaderLayout而滑出屏幕,粘連在頂部 | HeaderLayout直接子View |
custom_transformation | @string | 自定義Transformation的全路徑 | HeaderLayout直接子View |
extend_height | n(dp)|n%|0.n | 設置HeaderLayout的extendHeight,能夠是dimension、百分比數或者小數比例 | HeaderLayout |
transformation表示內置的幾中Transformation,可是想要自定義Transformation應該如何作呢?動畫
custom_transformation屬性則是專爲自定義Transformation而服務,其值爲本身實現的Transformation類的全路徑。自定義Transformation有兩種方式,其一是實現Transformation接口,另外一種方式是繼承TransformationAdapter類,TransformationAdapter是Transformation是Transformation接口的空實現,繼承於此則不須要實現全部的方法。url
interface Transformation<in V: View> {
/** * @see [HeaderLayout.scrollState]爲STATE_MIN_HEIGHT, 這個方法回調錶示[HeaderLayout]的Bottom已經收縮到了最小高度 * @param child 當前須要作變換的view * @param parent [HeaderLayout] * @param unConsumedDy 由其餘狀態到此狀態未消耗完的dy */
fun onStateMinHeight(child: V, parent: HeaderLayout, unConsumedDy: Int)
/** * @see [HeaderLayout.scrollState]爲STATE_NORMAL_PROCESS, 在STATE_MIN_HEIGHT和STATE_MAX_HEIGHT之間 * 這個方法回調錶示[HeaderLayout]的Bottom正在最小高度與最大高度之間 * @param child 當前須要作變換的view * @param parent [HeaderLayout] * @param percent 0<percent<1, 值爲([HeaderLayout.getBottom] - [HeaderLayout.minHeight]) / ([HeaderLayout.maxHeight] - [HeaderLayout.minHeight]]) * 且值不會爲0或者1, 爲0至關因而回調了[onStateMinHeight], 爲1至關於回調了[onStateMaxHeight], 因爲值不會爲0或1, * 因此在回調[onStateMinHeight]和[onStateMaxHeight]時會有一個未消耗的dy * @param dy 滑動的距離 */
fun onStateNormalProcess(child: V, parent: HeaderLayout, percent: Float, dy: Int)
/** * @see [HeaderLayout.scrollState]爲STATE_MAX_HEIGHT * 這個方法回調錶示[HeaderLayout]的Bottom正處於[HeaderLayout.maxHeight] * @param child 當前須要作變換的view * @param parent [HeaderLayout] * @param unConsumedDy 由其餘狀態到此狀態未消耗完的dy */
fun onStateMaxHeight(child: V, parent: HeaderLayout, unConsumedDy: Int)
/** * @see [HeaderLayout.scrollState]爲STATE_EXTEND_PROCESS, 在STATE_MAX_HEIGHT和STATE_EXTEND_MAX_END之間 * 這個方法回調錶示[HeaderLayout]的Bottom正處於[HeaderLayout.maxHeight] 和 [HeaderLayout.maxHeight] + [HeaderLayout.extendHeight]之間 * @param child 當前須要作變換的view * @param parent [HeaderLayout] * @param percent 0<percent<1, 值爲([HeaderLayout.getBottom] - [HeaderLayout.maxHeight]) / [HeaderLayout.extendHeight] * 且值不會爲0或者1, 爲0至關因而回調了[onStateMaxHeight], 爲1至關於回調了[onStateExtendMaxEnd], 因爲值不會爲0或1, * 因此在回調[onStateMaxHeight]和[onStateExtendMaxEnd]時會有一個未消耗的dy */
fun onStateExtendProcess(child: V, parent: HeaderLayout, percent: Float, dy: Int)
/** * @see [HeaderLayout.scrollState]爲STATE_EXTEND_MAX_END, * 這個方法回調錶示[HeaderLayout]的Bottom正處於[HeaderLayout.maxHeight] + [HeaderLayout.extendHeight] * @param child 當前須要作變換的view * @param parent [HeaderLayout] * @param unConsumedDy 由其餘狀態到此狀態未消耗完的dy * */
fun onStateExtendMaxEnd(child: V, parent: HeaderLayout, unConsumedDy: Int)
}
複製代碼
HeaderLayout在狀態變化的時候會遍歷子View的全部Transformation,也便是會回調Transformation接口中的這幾個方法,使用者能夠根據這幾個方法的含義來變換子View。spa
開發者在使用app:transformation和app:sticky_until_exit等屬性時,最好用AppCompatImageView代替ImageView,AppCompatTextView代替TextView,這樣在XML文件中則不會由於系統控件沒法使用自定義屬性而報紅線,即便報紅線也不會影響程序正常的執行,只是看着彆扭。
HeaderLayout是根據參照網易雲音樂的效果而實現的,但又跳出了「實現」的限制,提取出來了一個公共而又與業務無關的控件,其思想則是學習了CoordinatorLayout的behavior和ViewGroup事件的分發思想,將HeaderLayout的滑動狀態分發給其子View,從而產生聯動效果。
最後放上Github的地址把。HeaderLayout