高仿美團APP頁面滑動標(biāo)題欄漸變效果

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


摩拜單車
美團

實現(xiàn)思路:

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

好了,理清思路,我們就可以開始干活了!

先看下我們實現(xiàn)的效果:

效果圖

ObservableScrollView的實現(xiàn)

雖然谷歌官方給ScrollView提供了一個設(shè)置滑動監(jiān)聽方法setOnScrollChangeListener,不過這個方法需要基于API23之上(Android6.0系統(tǒng)),在日常開發(fā)中,我們需要對老系統(tǒng)用戶進行兼容(當(dāng)前兼容版本為Android4.1系統(tǒng)以上),所以這里我們需要去繼承ScrollView并把這個監(jiān)聽事件通過接口的方式對外暴露,這里把這個View取名為ObservableScrollView。

package com.lcw.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

/**
 * 重寫ScrollView對外拋出滑動監(jiān)聽數(shù)據(jù)
 * Create by: chenwei.li
 * Date: 2017/4/18
 * time: 10:09
 * Email: lichenwei.me@foxmail.com
 */
public class ObservableScrollView extends ScrollView {

    /**
     * 回調(diào)接口監(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);
    }


    /**
     * 添加回調(diào)接口,便于把滑動事件的數(shù)據(jù)向外拋
     */
    public interface OnObservableScrollViewListener {
        void onObservableScrollViewListener(int l, int t, int oldl, int oldt);
    }

    /**
     * 注冊回調(diào)接口監(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. 當(dāng)前滑動的x軸距離
     * @param t Current vertical scroll origin. 當(dāng)前滑動的y軸距離
     * @param oldl Previous horizontal scroll origin. 上一次滑動的x軸距離
     * @param oldt Previous vertical scroll origin. 上一次滑動的y軸距離
     */
    @Override
    protected 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);
        }
    }
}

開始變色的坐標(biāo)點

首先我們需要先了解Android系統(tǒng)的坐標(biāo)軸,這里不像我們以前學(xué)數(shù)學(xué)的坐標(biāo)軸一樣,在中心建軸,以中心向上和右為正方向,向下和左為負方向。
在Android系統(tǒng)里是以屏幕的左上角為原點(0,0),向右為X正軸,向下為Y正軸。

坐標(biāo)圖.png

知道了坐標(biāo)系,我們再來分解下效果圖:


效果圖1

效果圖2

效果圖3

一開始標(biāo)題欄和狀態(tài)欄呈透明色,隨著頁面的向上滑動標(biāo)題欄和狀態(tài)欄開始變色,當(dāng)A點達到B點的時候完成顏色的完整變化,這里我們只需要測量出A點到B點的距離,然后跟ScrollView所監(jiān)聽到的滑動距離相比對并作出相對應(yīng)的顏色變化,這樣就可以了,其中的A點到頂部的距離和B點到頂部的距離,我們可以通過ViewTreeObserver得到對應(yīng)的高度,然后相減即可。
實現(xiàn)代碼:

package com.lcw.view;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * 高仿美團APP頁面滑動標(biāo)題欄漸變效果
 * Create by: chenwei.li
 * Date: 2017/4/18
 * Time: 下午10:51
 * Email: lichenwei.me@foxmail.com
 */
public class MainActivity extends AppCompatActivity implements ObservableScrollView.OnObservableScrollViewListener {

    private ObservableScrollView mObservableScrollView;
    private TextView mImageView;
    private LinearLayout mHeaderContent;

    private int mHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //設(shè)置透明狀態(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);

        //獲取標(biāo)題欄高度
        ViewTreeObserver viewTreeObserver = mImageView.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                mHeight = mImageView.getHeight() - mHeaderContent.getHeight();//這里取的高度應(yīng)該為圖片的高度-標(biāo)題欄
                //注冊滑動監(jiān)聽
                mObservableScrollView.setOnObservableScrollViewListener(MainActivity.this);
            }
        });
    }


    /**
     * 獲取ObservableScrollView的滑動數(shù)據(jù)
     *
     * @param l
     * @param t
     * @param oldl
     * @param oldt
     */
    @Override
    public void onObservableScrollViewListener(int l, int t, int oldl, int oldt) {
        if (t <= 0) {
            //頂部圖處于最頂部,標(biāo)題欄透明
            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ū)域,標(biāo)題欄定色
            mHeaderContent.setBackgroundColor(Color.argb(255, 48, 63, 159));
        }
    }

}

透明狀態(tài)欄的實現(xiàn)

關(guān)于透明狀態(tài)欄這個東西,國內(nèi)很多人把它叫成沉浸式狀態(tài)欄,有人認同有人反對,因為谷歌官方對這個沒有具體的定義,所以這里我就不發(fā)表看法了,反正我們就是要讓狀態(tài)欄變成透明,然后內(nèi)容區(qū)域可以擴展到狀態(tài)欄,這樣就可以了。

1、首先我們需要對app的風(fēng)格進行設(shè)置:

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowActionBar">false</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

2、再來我們要對狀態(tài)欄進行透明化處理,其實這個東西我們也可以在xml里去做設(shè)置android:windowTranslucentStatus,不過現(xiàn)在國產(chǎn)手機滿天飛的ROM真是無奈,有些奇葩的機型是沒辦法識別的,所以這里為了更好的兼容,我們可以在代碼里面去實現(xiàn)。首先,我們需要判斷當(dāng)前的手機系統(tǒng)版本,4.4以上和5.0以上的處理方法是有區(qū)別的,具體實現(xiàn)代碼:

package com.lcw.view;

import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

/**
 * 設(shè)置系統(tǒng)狀態(tài)欄和導(dǎo)航欄透明化
 * Create by: chenwei.li
 * Date: 2017/4/18
 * Time: 下午11:01
 * Email: lichenwei.me@foxmail.com
 */

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);
        }
    }
}

關(guān)于上面的屬性設(shè)置,從字面上大家應(yīng)該也能看得懂,如果有不清楚的,可以查下谷歌方法提供的API:WindowManager.LayoutParams
在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)以后引入的,當(dāng)你的內(nèi)容需要顯示在系統(tǒng)作用域中時(比如頂部狀態(tài)欄,底部導(dǎo)航欄等),此時你需要在相關(guān)的第一個View屬性中添加該屬性,并設(shè)置屬性值為true,它會按照View的排列順序進行深度優(yōu)先的作用在View上。

3、實現(xiàn)了以上的操作后,我們基本上已經(jīng)成功了,此時你會發(fā)現(xiàn)你的標(biāo)題欄已經(jīng)可以擴展到透明的系統(tǒng)狀態(tài)欄了,不過此時你會發(fā)現(xiàn),狀態(tài)欄離你的標(biāo)題欄太靠近了,如果你想實現(xiàn)和上面效果圖一樣的效果,只需要在你的標(biāo)題欄添加一個paddingTop值就可以,一般系統(tǒng)是25dp,當(dāng)然如果你采用代碼動態(tài)獲取的方式也是可以的,這樣可以做好更好的適配,畢竟國內(nèi)各大廠商都有自己的一套標(biāo)準(zhǔn)。
具體實現(xiàn)代碼:
activity_mian.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.lcw.view.ObservableScrollView
        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">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                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" />

            <TextView
                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" />

        </LinearLayout>
    </com.lcw.view.ObservableScrollView>


    <include layout="@layout/include_header_itl" />

</RelativeLayout>

include_header_itl.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="horizontal"
        android:paddingTop="25dp">

        <ImageView
            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" />


        <TextView
            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="我是標(biāo)題"
            android:textColor="#ffffff"
            android:textSize="16sp"
            android:textStyle="bold" />


        <ImageView
            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" />

    </RelativeLayout>

</LinearLayout>

源碼下載:

好了,到這里就結(jié)束了,這里附上源碼地址(歡迎Star,歡迎Fork):源碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,662評論 4 61
  • 葛根湯的主治非常簡單:“項背強幾幾”。 不要以為“項背強幾幾”就是大椎穴那里不舒服,我們要把“項背”延伸,頭項、腰...
    逍遙an閱讀 4,236評論 0 2
  • 我喜歡多肉,看似簡單,但是需要去呵護的小植物,一不小心他就和你鬧脾氣。
    _邊邊閱讀 244評論 0 1
  • 昨天跟幾位好友聊天大家好久沒見所以聊的比較盡興從工作聊到生活從大齡剩女聊到兩性關(guān)系有兩位好友位列成功女性目前都是自...
    承謙閱讀 693評論 0 1

友情鏈接更多精彩內(nèi)容