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

摘要:

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

(本文是對本次工作的踩坑總結)

頁面效果:

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

  1. 點擊“眼睛圖標”,實現顯示/隱藏各類資金 (老版有此功能,本次做些調整)

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

app.gif

實現策略:

功能一:點擊“眼睛圖標”,實現顯示/隱藏各類資金

第1個功能點很簡單,其實就是監聽“眼睛圖標”的點擊事件,將需要顯示的“數字”顯示成固定文本“ **** ”并將“開眼圖標”設置成“閉眼圖標”。點擊“閉眼圖標”時,對之前的操作進行還原。

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

當筆者在點擊“眼睛圖標”時,發現Toolbar將點擊事件攔截了,事件無法傳遞到下層布局......

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

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很多頁面都有這種效果,還是換種方式實現吧...

最后發表一下感慨:

mind.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容