最近公司的App作了一次改版,對UI頁面作了一些用戶體驗上的優化。android
(本文是對本次工做的踩坑總結)程序員
本次App改版,筆者這牽扯到兩個功能點的實現:app
點擊「眼睛圖標」,實現顯示/隱藏各種資金 (老版有此功能,本次作些調整)ide
實現導航欄背景圖漸變佈局
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 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。