Android - Toolbar事件穿透和背景漸變

摘要:

最近公司的App作了一次改版,對UI頁面作了一些用戶體驗上的優化。android

(本文是對本次工做的踩坑總結)程序員

頁面效果:

本次App改版,筆者這牽扯到兩個功能點的實現:app

  1. 點擊「眼睛圖標」,實現顯示/隱藏各種資金 (老版有此功能,本次作些調整)ide

  2. 實現導航欄背景圖漸變佈局

app.gif測試

實現策略:

功能一:點擊「眼睛圖標」,實現顯示/隱藏各種資金

第1個功能點很簡單,其實就是監聽「眼睛圖標」的點擊事件,將須要顯示的「數字」顯示成固定文本「 **** 」並將「開眼圖標」設置成「閉眼圖標」。點擊「閉眼圖標」時,對以前的操做進行還原。優化

理論上是這樣沒有錯,但實際操做起來就會發現有坑了/(ㄒoㄒ)/~spa

當筆者在點擊「眼睛圖標」時,發現Toolbar將點擊事件攔截了,事件沒法傳遞到下層佈局......code

那麼怎樣才能穿透Toolbar將點擊事件分發到下面的佈局呢?xml

Toolbar是一個懸浮在最上層的、獨立的ViewGroup,裏面沒有包含下層的內容佈局。並且Toolbar源碼中onTouchEvent方法的返回值爲true,意味着事件最多傳遞到這就會被消費掉。

Toolbar.png

難道要自定義Toolbar,重寫onTouchEvent方法?這是筆者最初的想法,可是後來想了想,這種修改源碼的方式有點做死(由於筆者沒有詳細看過Toolbar的源碼,並且就算看過Toolbar的源碼,源碼後期發生變動時還要維護)。

筆者的XML佈局層次(簡化版):

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout >

    <!-- 內容部分 -->
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout">
        
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view" />      
    </android.support.v4.widget.SwipeRefreshLayout>

    <!-- 標題欄 -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
    </android.support.v7.widget.Toolbar>
</RelativeLayout>

咱們能夠看到「標題欄」和「內容部分」在佈局上是「平行關係」,但實際在視圖上「標題欄」是在「內容部分」的上方。

尋找解決辦法:

筆者思考了很長時間,想出了一個相對靠譜的解決辦法:Toolbar執行到onTouchEvent以後,事件就被消費了。咱們能夠在這以前搞點事情,好比改變事件流的傳遞方向,將事件流向下層佈局分發

實現事件穿透的代碼(僅包含觸摸監聽器的實現):

 

// 穿透Toolbar的點擊事件,向下層分發處理
mToolbarOnTouchListener = new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return findViewById(R.id.swipe_refresh_layout).dispatchTouchEvent(event);
    }
};

略懂事件傳遞機制的Android程序員應該都知道,View觸摸監聽器的onTouch方法會在onTouchEvent方法執行完成以前執行,那麼咱們能夠在onTouch方法裏再進行1次事件分發,調用「內容部分」的最外層佈局SwipeRefreshLayout的分發方法dispatchTouchEvent(MotionEvent event),並將正在Toolbar中進行傳遞的event事件做爲參數傳遞進去,讓SwipeRefreshLayout繼續處理該事件,從而實現事件穿透

解決辦法終於找到了,那麼直接上來就給Toolbar設置完這個觸摸監聽器就萬事大吉了嗎?

咱們還作了一個背景圖片漸變的功能,Toolbar背景不可見或半透明時響應下層佈局的點擊事件很正常,很容易理解。可是背景圖片若是徹底可見,點擊Toolbar還響應下層佈局的點擊事件,是否是用戶體驗上有點彆扭呢?(全都是細節)

最終的解決方案 (動態給Toolbar設置監聽器):

  • 當背景圖片徹底可見以前,給Toolbar設置觸摸監聽器mToolbarOnTouchListener。

    mToolbar.setOnTouchListener(mToolbarOnTouchListener);
  • 當背景圖片徹底可見以後,將Toolbar的觸摸監聽器設置爲null。

    mToolbar.setOnTouchListener(null);

至此,第一個問題就描述完了。

功能二:實現導航欄背景圖漸變

以前筆者作過Toolbar背景顏色漸變的效果,但筆者的App基本上每一個頁面的Toolbar背景用的都是圖片,不是顏色。仔細想了想,圖片的漸變也只能用透明度來搞了。

在網上百度&谷歌了一下,終於找到了設置圖片透明度的方法(大體3步)

 

// 獲取Drawable對象
Drawable mDrawable = ContextCompat.getDrawable(mActivity, R.drawable.lcs_actionbar_bg2);
// 設置Drawable的透明度
mDrawable.setAlpha(255);
// 給Toolbar設置背景圖
mToolbar.setBackgroundDrawable(mDrawable);

有了解決方案終於能夠開工了:

 

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    // 獲取背景圖片
    Drawable mDrawable = ContextCompat.getDrawable(mActivity, R.drawable.lcs_actionbar_bg2);

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        
        // 省略了與漸變背景圖無關的代碼
        ......
        
        int toolBarHeight = mToolbar.getMeasuredHeight();
        if ((recyclerView.computeVerticalScrollOffset()) >= (toolBarHeight * 2.5)) { // >=Toolbar高度的2.5倍時全顯背景圖
            mDrawable.setAlpha(255);
            mToolbar.setBackgroundDrawable(mDrawable);
            mToolbar.setOnTouchListener(null);
        } else if((recyclerView.computeVerticalScrollOffset()) >= toolBarHeight){ // >=Toolbar高度&&<Toolbar高度的2.5倍時開始漸變背景圖
            mDrawable.setAlpha((int) (255 * ((recyclerView.computeVerticalScrollOffset() - toolBarHeight)/(toolBarHeight * 1.5F))));
            mToolbar.setBackgroundDrawable(mDrawable);
            mToolbar.setOnTouchListener(mToolbarOnTouchListener);
        } else { // 小於Toolbar高度時不設置背景圖
            mToolbar.setBackgroundDrawable(null);
            mToolbar.setOnTouchListener(mToolbarOnTouchListener);
        }
    }
});

RecyclerView.computeVerticalScrollOffset() 能夠返回給咱們recyclerView豎直滾動的距離。

筆者給RecyclerView添加了一個滾動監聽器,根據RecyclerView豎直滾動的距離動態決定背景圖片的透明度,具體細節以下:

  • 當RecyclerView豎直滾動距離<Toolbar的高度時,不設置背景圖(Toolbar背景透明),並設置上觸摸監聽器(穿透Toolbar點擊事件)。

  • 當RecyclerView豎直滾動距離>=Toolbar的高度且<2.5倍的Toolbar高度時,漸變背景圖,並設置上觸摸監聽器(穿透Toolbar點擊事件)。

  • RecyclerView豎直滾動距離>=2.5倍的Toolbar高度時,全顯背景圖,觸摸監聽器設置爲null。

接着運行了一下,發現已經實現了文章開始時的動態圖效果。

以後,放心的打包完App提交給測試MM測試了。但好景不長,測試MM那測試機類型不少,在不少華爲手機上測出了一個Bug,以下圖所示:

app_bug.gif

能夠看到,在華爲手機上,筆者的APP中全部用到該背景圖的地方都會受到透明度影響。

後來從網上查閱了一些資料,才知道原來華爲手機是作了一些優化處理的。同一張圖片(好比R.drawable.lcs_actionbar_bg)在被轉化成Drawable對象並在內存中使用時會被「共享」,意味着筆者App中全部對這個Drawable(R.drawable.lcs_actionbar_bg)的操做全都是在對同一個對象進行操做。

華爲手機的這種處理方式減小多餘對象的生成,減小了內存暫用,減小了GC,仔細想一想仍是挺讚的。(好像扯遠了,該回歸正題想一想怎麼處理了o(╯□╰)o....)

一開始想了想,以爲能夠這樣處理:在離開這個頁面時,記錄一下透明度,把Drawable背景圖設置爲徹底可見的;回到這個頁面時,根據記錄值還原Drawable的透明度。

理想是美好的,現實是殘酷的。這個頁面的管理器是一個Fragment,筆者在onStop方法裏作了上述這種處理,但仍是會偶發出現這種透明度的Bug,看來筆者還有不少細節沒有考慮到(怎麼感受這細節越陷越深了/(ㄒoㄒ)/~~)。

最終的解決辦法:

筆者無奈之下放棄了以前的處理方式,想了一個投機取巧的辦法:

把資源文件 lcs_actionbar_bg.png 又複製了一份,命名爲lcs_actionbar_bg2.png

lcs_actionbar_bg2.png

lcs_actionbar_bg2.png 這張圖片獨立爲這個導航欄漸變的頁面服務,不會影響到其餘界面。

最後,華爲手機導航欄透明度的Bug終於解決了。

題外話

有朋友問:搞個標題欄特效爲何不用 AppBarLayout + CollapsingToolbarLayou + Toolbar 作個摺疊效果?這個應該很簡單吧?

答:產品說咱們的App不少頁面都有這種效果,仍是換種方式實現吧...

最後發表一下感慨:

做者:夢想編織者灬小楠 連接:https://www.jianshu.com/p/65591a718cdc 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索