Android仿美團滑動標題欄漸變效果
最近公司的項目剛好需要這個效果,雖然GitHub上有很多成型的開源項目,不過都附帶了很多其他的東西,為了這個效果去引用一個第三方庫明顯不合適,所以就決定自力更生了。
其實在日常軟件中還是挺常見的,比如帶Banner廣告圖的首頁或者是帶頭部的個人中心,下面是美團的實現(xiàn)效果:

實現(xiàn)思路:
看下美團的效果圖,其實就可以很明顯的發(fā)現(xiàn)頭部(標題欄+狀態(tài)欄)其實是沒有動的,只是一開始呈透明裝,然后隨著內(nèi)容的向上滑動在某個坐標點開始漸變顏色。
基于這樣的思考,我們把代碼實現(xiàn)拆分3點:
1、整體界面是個ScrollView,并且我們需要對滑動進行監(jiān)聽
2、確定坐標點,在什么時候開始變色,在什么時候停止變色
3、需要將狀態(tài)欄透明化,并且讓我們的內(nèi)容區(qū)域可以擴展到狀態(tài)欄
好了,理清思路,我們就可以開始干活了!
先看下我們實現(xiàn)的效果:

ObservableScrollView的實現(xiàn)
雖然谷歌官方給ScrollView提供了一個設置滑動監(jiān)聽方法setOnScrollChangeListener,不過這個方法需要基于API23之上(Android6.0系統(tǒng)),在日常開發(fā)中,我們需要對老系統(tǒng)用戶進行兼容(當前兼容版本為Android4.1系統(tǒng)以上),所以這里我們需要去繼承ScrollView并把這個監(jiān)聽事件通過接口的方式對外暴露,這里把這個View取名為ObservableScrollView。
/**?*?重寫ScrollView對外拋出滑動監(jiān)聽數(shù)據(jù)*/public class ObservableScrollView extends ScrollView {/*** 回調接口監(jiān)聽事件*/private OnObservableScrollViewListener mOnObservableScrollViewListener;public ObservableScrollView(Context context) {super(context);}public ObservableScrollView(Context context, AttributeSet attrs) {super(context, attrs);}public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 添加回調接口,便于把滑動事件的數(shù)據(jù)向外拋*/public interface OnObservableScrollViewListener {void onObservableScrollViewListener(int l, int t, int oldl, int oldt);}/*** 注冊回調接口監(jiān)聽事件** @param onObservableScrollViewListener*/public void setOnObservableScrollViewListener(OnObservableScrollViewListener onObservableScrollViewListener) {this.mOnObservableScrollViewListener = onObservableScrollViewListener;}/*** This is called in response to an internal scroll in this view (i.e., the* view scrolled its own contents). This is typically as a result of* {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been* called.** @param l Current horizontal scroll origin. 當前滑動的x軸距離* @param t Current vertical scroll origin. 當前滑動的y軸距離* @param oldl Previous horizontal scroll origin. 上一次滑動的x軸距離* @param oldt Previous vertical scroll origin. 上一次滑動的y軸距離*/@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (mOnObservableScrollViewListener != null) {//將監(jiān)聽到的數(shù)據(jù)向外拋mOnObservableScrollViewListener.onObservableScrollViewListener(l, t, oldl, oldt);}}}
開始變色的坐標點
首先我們需要先了解Android系統(tǒng)的坐標軸,這里不像我們以前學數(shù)學的坐標軸一樣,在中心建軸,以中心向上和右為正方向,向下和左為負方向。
在Android系統(tǒng)里是以屏幕的左上角為原點(0,0),向右為X正軸,向下為Y正軸。

知道了坐標系,我們再來分解下效果圖:



一開始標題欄和狀態(tài)欄呈透明色,隨著頁面的向上滑動標題欄和狀態(tài)欄開始變色,當A點達到B點的時候完成顏色的完整變化,這里我們只需要測量出A點到B點的距離,然后跟ScrollView所監(jiān)聽到的滑動距離相比對并作出相對應的顏色變化,這樣就可以了,其中的A點到頂部的距離和B點到頂部的距離,我們可以通過ViewTreeObserver得到對應的高度,然后相減即可。
實現(xiàn)代碼:
/**?*?高仿美團APP頁面滑動標題欄漸變效果*/public class MainActivity extends AppCompatActivity implements ObservableScrollView.OnObservableScrollViewListener {private ObservableScrollView mObservableScrollView;private TextView mImageView;private LinearLayout mHeaderContent;private int mHeight;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//設置透明狀態(tài)欄StatusbarUtils.enableTranslucentStatusbar(this);setContentView(R.layout.activity_main);//初始化控件mObservableScrollView = (ObservableScrollView) findViewById(R.id.sv_main_content);mImageView = (TextView) findViewById(R.id.iv_main_topImg);mHeaderContent = (LinearLayout) findViewById(R.id.ll_header_content);//獲取標題欄高度ViewTreeObserver viewTreeObserver = mImageView.getViewTreeObserver();viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);mHeight = mImageView.getHeight() - mHeaderContent.getHeight();//這里取的高度應該為圖片的高度-標題欄//注冊滑動監(jiān)聽mObservableScrollView.setOnObservableScrollViewListener(MainActivity.this);}});}/*** 獲取ObservableScrollView的滑動數(shù)據(jù)** @param l* @param t* @param oldl* @param oldt*/@Overridepublic void onObservableScrollViewListener(int l, int t, int oldl, int oldt) {if (t <= 0) {//頂部圖處于最頂部,標題欄透明mHeaderContent.setBackgroundColor(Color.argb(0, 48, 63, 159));} else if (t > 0 && t < mHeight) {//滑動過程中,漸變float scale = (float) t / mHeight;//算出滑動距離比例float alpha = (255 * scale);//得到透明度mHeaderContent.setBackgroundColor(Color.argb((int) alpha, 48, 63, 159));} else {//過頂部圖區(qū)域,標題欄定色mHeaderContent.setBackgroundColor(Color.argb(255, 48, 63, 159));}}}
透明狀態(tài)欄的實現(xiàn)
關于透明狀態(tài)欄這個東西,國內(nèi)很多人把它叫成沉浸式狀態(tài)欄,有人認同有人反對,因為谷歌官方對這個沒有具體的定義,所以這里我就不發(fā)表看法了,反正我們就是要讓狀態(tài)欄變成透明,然后內(nèi)容區(qū)域可以擴展到狀態(tài)欄,這樣就可以了。
1、首先我們需要對app的風格進行設置:
- @color/colorPrimary
- @color/colorPrimaryDark
- @color/colorAccent
- false
- false
- true
2、再來我們要對狀態(tài)欄進行透明化處理,其實這個東西我們也可以在xml里去做設置android:windowTranslucentStatus,不過現(xiàn)在國產(chǎn)手機滿天飛的ROM真是無奈,有些奇葩的機型是沒辦法識別的,所以這里為了更好的兼容,我們可以在代碼里面去實現(xiàn)。首先,我們需要判斷當前的手機系統(tǒng)版本,4.4以上和5.0以上的處理方法是有區(qū)別的,具體實現(xiàn)代碼:
/**?*?設置系統(tǒng)狀態(tài)欄和導航欄透明化*/public class StatusbarUtils {/*** 啟用 透明狀態(tài)欄** @param activity*/public static void enableTranslucentStatusbar(Activity activity) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {Window window = activity.getWindow();window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Window window = activity.getWindow();window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(Color.TRANSPARENT);window.setNavigationBarColor(Color.TRANSPARENT);}}}
在4.4以上及5.0以下的系統(tǒng)狀態(tài)欄會成半透明狀,在5.0及以上的系統(tǒng)可以實現(xiàn)完全透明。
如果此時你運行了代碼你會發(fā)現(xiàn),雖然狀態(tài)欄透明化了,但是我們的布局內(nèi)容并沒有擴展到狀態(tài)欄中,這里需要而外的提到一個屬性fitsSystemWindows它是在Android4.4系統(tǒng)以后引入的,當你的內(nèi)容需要顯示在系統(tǒng)作用域中時(比如頂部狀態(tài)欄,底部導航欄等),此時你需要在相關的第一個View屬性中添加該屬性,并設置屬性值為true,它會按照View的排列順序進行深度優(yōu)先的作用在View上。
3、實現(xiàn)了以上的操作后,我們基本上已經(jīng)成功了,此時你會發(fā)現(xiàn)你的標題欄已經(jīng)可以擴展到透明的系統(tǒng)狀態(tài)欄了,不過此時你會發(fā)現(xiàn),狀態(tài)欄離你的標題欄太靠近了,如果你想實現(xiàn)和上面效果圖一樣的效果,只需要在你的標題欄添加一個paddingTop值就可以,一般系統(tǒng)是25dp,當然如果你采用代碼動態(tài)獲取的方式也是可以的,這樣可以做好更好的適配,畢竟國內(nèi)各大廠商都有自己的一套標準。
具體實現(xiàn)代碼:
activity_mian.xml
xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent">android:id="@+id/sv_main_content"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:layout_alignParentStart="true"android:layout_alignParentTop="true"android:scrollbars="none">android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">android:id="@+id/tv_main_topContent"android:layout_width="match_parent"android:layout_height="280dp"android:background="#b5b433"android:gravity="center"android:src="@mipmap/ic_launcher"android:text="我是頭部"android:textSize="22sp" />android:layout_width="match_parent"android:layout_height="600dp"android:background="#ffffff"android:gravity="center"android:src="@mipmap/ic_launcher"android:text="我是內(nèi)容"android:textSize="22sp" />????
include_header_itl.xml
android:id="@+id/ll_header_content"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#00000000"android:fitsSystemWindows="true"android:orientation="vertical">android:layout_width="match_parent"android:layout_height="80dp"android:orientation="horizontal"android:paddingTop="25dp">android:id="@+id/iv_header_left"android:layout_width="40dp"android:layout_height="match_parent"android:paddingLeft="8dp"android:paddingRight="12dp"android:src="@mipmap/icon_header_back" />android:id="@+id/tv_header_title"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_centerHorizontal="true"android:ellipsize="end"android:gravity="center"android:maxLines="2"android:text="我是標題"android:textColor="#ffffff"android:textSize="16sp"android:textStyle="bold" />android:id="@+id/iv_header_img"android:layout_width="40dp"android:layout_height="match_parent"android:layout_alignParentRight="true"android:layout_gravity="center"android:paddingLeft="12dp"android:paddingRight="8dp"android:src="@mipmap/icon_header_kefu" />
源碼地址:
https://github.com/Lichenwei-Dev/ObservableScrollView
到這里就結束啦。
