高仿美團(tuán)APP頁(yè)面滑動(dòng)標(biāo)題欄漸變效果

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


摩拜單車
美團(tuán)

實(shí)現(xiàn)思路:

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

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

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

效果圖

ObservableScrollView的實(shí)現(xiàn)

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

package com.lcw.view;

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

/**
 * 重寫ScrollView對(duì)外拋出滑動(dòng)監(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)接口,便于把滑動(dòng)事件的數(shù)據(jù)向外拋
     */
    public interface OnObservableScrollViewListener {
        void onObservableScrollViewListener(int l, int t, int oldl, int oldt);
    }

    /**
     * 注冊(cè)回調(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)前滑動(dòng)的x軸距離
     * @param t Current vertical scroll origin. 當(dāng)前滑動(dòng)的y軸距離
     * @param oldl Previous horizontal scroll origin. 上一次滑動(dòng)的x軸距離
     * @param oldt Previous vertical scroll origin. 上一次滑動(dòng)的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)點(diǎn)

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

坐標(biāo)圖.png

知道了坐標(biāo)系,我們?cè)賮?lái)分解下效果圖:


效果圖1

效果圖2

效果圖3

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

/**
 * 高仿美團(tuán)APP頁(yè)面滑動(dòng)標(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)題欄
                //注冊(cè)滑動(dòng)監(jiān)聽
                mObservableScrollView.setOnObservableScrollViewListener(MainActivity.this);
            }
        });
    }


    /**
     * 獲取ObservableScrollView的滑動(dòng)數(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) {
            //滑動(dòng)過(guò)程中,漸變
            float scale = (float) t / mHeight;//算出滑動(dòng)距離比例
            float alpha = (255 * scale);//得到透明度
            mHeaderContent.setBackgroundColor(Color.argb((int) alpha, 48, 63, 159));
        } else {
            //過(guò)頂部圖區(qū)域,標(biāo)題欄定色
            mHeaderContent.setBackgroundColor(Color.argb(255, 48, 63, 159));
        }
    }

}

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

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

1、首先我們需要對(duì)app的風(fēng)格進(jìn)行設(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、再來(lái)我們要對(duì)狀態(tài)欄進(jìn)行透明化處理,其實(shí)這個(gè)東西我們也可以在xml里去做設(shè)置android:windowTranslucentStatus,不過(guò)現(xiàn)在國(guó)產(chǎn)手機(jī)滿天飛的ROM真是無(wú)奈,有些奇葩的機(jī)型是沒辦法識(shí)別的,所以這里為了更好的兼容,我們可以在代碼里面去實(shí)現(xiàn)。首先,我們需要判斷當(dāng)前的手機(jī)系統(tǒng)版本,4.4以上和5.0以上的處理方法是有區(qū)別的,具體實(shí)現(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)欄會(huì)成半透明狀,在5.0及以上的系統(tǒng)可以實(shí)現(xiàn)完全透明。
如果此時(shí)你運(yùn)行了代碼你會(huì)發(fā)現(xiàn),雖然狀態(tài)欄透明化了,但是我們的布局內(nèi)容并沒有擴(kuò)展到狀態(tài)欄中,這里需要而外的提到一個(gè)屬性fitsSystemWindows它是在Android4.4系統(tǒng)以后引入的,當(dāng)你的內(nèi)容需要顯示在系統(tǒng)作用域中時(shí)(比如頂部狀態(tài)欄,底部導(dǎo)航欄等),此時(shí)你需要在相關(guān)的第一個(gè)View屬性中添加該屬性,并設(shè)置屬性值為true,它會(huì)按照View的排列順序進(jìn)行深度優(yōu)先的作用在View上。

3、實(shí)現(xiàn)了以上的操作后,我們基本上已經(jīng)成功了,此時(shí)你會(huì)發(fā)現(xiàn)你的標(biāo)題欄已經(jīng)可以擴(kuò)展到透明的系統(tǒng)狀態(tài)欄了,不過(guò)此時(shí)你會(huì)發(fā)現(xiàn),狀態(tài)欄離你的標(biāo)題欄太靠近了,如果你想實(shí)現(xiàn)和上面效果圖一樣的效果,只需要在你的標(biāo)題欄添加一個(gè)paddingTop值就可以,一般系統(tǒng)是25dp,當(dāng)然如果你采用代碼動(dòng)態(tài)獲取的方式也是可以的,這樣可以做好更好的適配,畢竟國(guó)內(nèi)各大廠商都有自己的一套標(biāo)準(zhǔn)。
具體實(shí)現(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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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

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