摘要:
最近公司的App做了一次改版凳干,對(duì)UI頁(yè)面做了一些用戶體驗(yàn)上的優(yōu)化枕面。
(本文是對(duì)本次工作的踩坑總結(jié))
頁(yè)面效果:
本次App改版嗅绰,筆者這牽扯到兩個(gè)功能點(diǎn)的實(shí)現(xiàn):
點(diǎn)擊“眼睛圖標(biāo)”娘侍,實(shí)現(xiàn)顯示/隱藏各類資金 (老版有此功能大溜,本次做些調(diào)整)
實(shí)現(xiàn)導(dǎo)航欄背景圖漸變
實(shí)現(xiàn)策略:
功能一:點(diǎn)擊“眼睛圖標(biāo)”技矮,實(shí)現(xiàn)顯示/隱藏各類資金
第1個(gè)功能點(diǎn)很簡(jiǎn)單抖誉,其實(shí)就是監(jiān)聽“眼睛圖標(biāo)”的點(diǎn)擊事件,將需要顯示的“數(shù)字”顯示成固定文本“ **** ”并將“開眼圖標(biāo)”設(shè)置成“閉眼圖標(biāo)”衰倦。點(diǎn)擊“閉眼圖標(biāo)”時(shí)袒炉,對(duì)之前的操作進(jìn)行還原。
理論上是這樣沒(méi)有錯(cuò)樊零,但實(shí)際操作起來(lái)就會(huì)發(fā)現(xiàn)有坑了/(ㄒoㄒ)/~
當(dāng)筆者在點(diǎn)擊“眼睛圖標(biāo)”時(shí)我磁,發(fā)現(xiàn)Toolbar將點(diǎn)擊事件攔截了孽文,事件無(wú)法傳遞到下層布局......
那么怎樣才能穿透Toolbar將點(diǎn)擊事件分發(fā)到下面的布局呢?
Toolbar是一個(gè)懸浮在最上層的夺艰、獨(dú)立的ViewGroup芋哭,里面沒(méi)有包含下層的內(nèi)容布局。而且Toolbar源碼中onTouchEvent方法的返回值為true郁副,意味著事件最多傳遞到這就會(huì)被消費(fèi)掉减牺。
難道要自定義Toolbar,重寫onTouchEvent方法存谎?這是筆者最初的想法拔疚,但是后來(lái)想了想,這種修改源碼的方式有點(diǎn)作死(因?yàn)楣P者沒(méi)有詳細(xì)看過(guò)Toolbar的源碼既荚,而且就算看過(guò)Toolbar的源碼稚失,源碼后期發(fā)生變更時(shí)還要維護(hù))。
筆者的XML布局層次(簡(jiǎn)化版):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout >
<!-- 內(nèi)容部分 -->
<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>
<!-- 標(biāo)題欄 -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
</android.support.v7.widget.Toolbar>
</RelativeLayout>
我們可以看到“標(biāo)題欄”和“內(nèi)容部分”在布局上是“平行關(guān)系”固以,但實(shí)際在視圖上“標(biāo)題欄”是在“內(nèi)容部分”的上方墩虹。
尋找解決辦法:
筆者思考了很長(zhǎng)時(shí)間,想出了一個(gè)相對(duì)靠譜的解決辦法:Toolbar執(zhí)行到onTouchEvent之后憨琳,事件就被消費(fèi)了诫钓。我們可以在這之前搞點(diǎn)事情,比如改變事件流的傳遞方向篙螟,將事件流向下層布局分發(fā)菌湃。
實(shí)現(xiàn)事件穿透的代碼(僅包含觸摸監(jiān)聽器的實(shí)現(xiàn)):
// 穿透Toolbar的點(diǎn)擊事件,向下層分發(fā)處理
mToolbarOnTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return findViewById(R.id.swipe_refresh_layout).dispatchTouchEvent(event);
}
};
略懂事件傳遞機(jī)制的Android程序員應(yīng)該都知道,View觸摸監(jiān)聽器的onTouch方法會(huì)在onTouchEvent方法執(zhí)行完成之前執(zhí)行遍略,那么我們可以在onTouch方法里再進(jìn)行1次事件分發(fā)惧所,調(diào)用“內(nèi)容部分”的最外層布局SwipeRefreshLayout的分發(fā)方法dispatchTouchEvent(MotionEvent event),并將正在Toolbar中進(jìn)行傳遞的event事件作為參數(shù)傳遞進(jìn)去绪杏,讓SwipeRefreshLayout繼續(xù)處理該事件下愈,從而實(shí)現(xiàn)事件穿透。
解決辦法終于找到了蕾久,那么直接上來(lái)就給Toolbar設(shè)置完這個(gè)觸摸監(jiān)聽器就萬(wàn)事大吉了嗎势似?
我們還做了一個(gè)背景圖片漸變的功能,Toolbar背景不可見或半透明時(shí)響應(yīng)下層布局的點(diǎn)擊事件很正常僧著,很容易理解履因。但是背景圖片如果完全可見,點(diǎn)擊Toolbar還響應(yīng)下層布局的點(diǎn)擊事件盹愚,是不是用戶體驗(yàn)上有點(diǎn)別扭呢栅迄?(全都是細(xì)節(jié))
最終的解決方案 (動(dòng)態(tài)給Toolbar設(shè)置監(jiān)聽器):
-
當(dāng)背景圖片完全可見之前,給Toolbar設(shè)置觸摸監(jiān)聽器mToolbarOnTouchListener皆怕。
mToolbar.setOnTouchListener(mToolbarOnTouchListener);
-
當(dāng)背景圖片完全可見之后毅舆,將Toolbar的觸摸監(jiān)聽器設(shè)置為null西篓。
mToolbar.setOnTouchListener(null);
至此,第一個(gè)問(wèn)題就描述完了憋活。
功能二:實(shí)現(xiàn)導(dǎo)航欄背景圖漸變
之前筆者做過(guò)Toolbar背景顏色漸變的效果污淋,但筆者的App基本上每個(gè)頁(yè)面的Toolbar背景用的都是圖片,不是顏色余掖。仔細(xì)想了想寸爆,圖片的漸變也只能用透明度來(lái)搞了。
在網(wǎng)上百度&谷歌了一下盐欺,終于找到了設(shè)置圖片透明度的方法(大致3步)
// 獲取Drawable對(duì)象
Drawable mDrawable = ContextCompat.getDrawable(mActivity, R.drawable.lcs_actionbar_bg2);
// 設(shè)置Drawable的透明度
mDrawable.setAlpha(255);
// 給Toolbar設(shè)置背景圖
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);
// 省略了與漸變背景圖無(wú)關(guān)的代碼
......
int toolBarHeight = mToolbar.getMeasuredHeight();
if ((recyclerView.computeVerticalScrollOffset()) >= (toolBarHeight * 2.5)) { // >=Toolbar高度的2.5倍時(shí)全顯背景圖
mDrawable.setAlpha(255);
mToolbar.setBackgroundDrawable(mDrawable);
mToolbar.setOnTouchListener(null);
} else if((recyclerView.computeVerticalScrollOffset()) >= toolBarHeight){ // >=Toolbar高度&&<Toolbar高度的2.5倍時(shí)開始漸變背景圖
mDrawable.setAlpha((int) (255 * ((recyclerView.computeVerticalScrollOffset() - toolBarHeight)/(toolBarHeight * 1.5F))));
mToolbar.setBackgroundDrawable(mDrawable);
mToolbar.setOnTouchListener(mToolbarOnTouchListener);
} else { // 小于Toolbar高度時(shí)不設(shè)置背景圖
mToolbar.setBackgroundDrawable(null);
mToolbar.setOnTouchListener(mToolbarOnTouchListener);
}
}
});
RecyclerView.computeVerticalScrollOffset() 可以返回給我們r(jià)ecyclerView豎直滾動(dòng)的距離赁豆。
筆者給RecyclerView添加了一個(gè)滾動(dòng)監(jiān)聽器,根據(jù)RecyclerView豎直滾動(dòng)的距離動(dòng)態(tài)決定背景圖片的透明度冗美,具體細(xì)節(jié)如下:
當(dāng)RecyclerView豎直滾動(dòng)距離<Toolbar的高度時(shí)魔种,不設(shè)置背景圖(Toolbar背景透明),并設(shè)置上觸摸監(jiān)聽器(穿透Toolbar點(diǎn)擊事件)粉洼。
當(dāng)RecyclerView豎直滾動(dòng)距離>=Toolbar的高度且<2.5倍的Toolbar高度時(shí)节预,漸變背景圖,并設(shè)置上觸摸監(jiān)聽器(穿透Toolbar點(diǎn)擊事件)属韧。
RecyclerView豎直滾動(dòng)距離>=2.5倍的Toolbar高度時(shí)安拟,全顯背景圖,觸摸監(jiān)聽器設(shè)置為null宵喂。
接著運(yùn)行了一下糠赦,發(fā)現(xiàn)已經(jīng)實(shí)現(xiàn)了文章開始時(shí)的動(dòng)態(tài)圖效果。
之后锅棕,放心的打包完App提交給測(cè)試MM測(cè)試了拙泽。但好景不長(zhǎng),測(cè)試MM那測(cè)試機(jī)類型很多裸燎,在很多華為手機(jī)上測(cè)出了一個(gè)Bug顾瞻,如下圖所示:
可以看到,在華為手機(jī)上德绿,筆者的APP中所有用到該背景圖的地方都會(huì)受到透明度影響荷荤。
后來(lái)從網(wǎng)上查閱了一些資料,才知道原來(lái)華為手機(jī)是做了一些優(yōu)化處理的脆炎。同一張圖片(比如R.drawable.lcs_actionbar_bg)在被轉(zhuǎn)化成Drawable對(duì)象并在內(nèi)存中使用時(shí)會(huì)被“共享”梅猿,意味著筆者App中所有對(duì)這個(gè)Drawable(R.drawable.lcs_actionbar_bg)的操作全都是在對(duì)同一個(gè)對(duì)象進(jìn)行操作氓辣。
華為手機(jī)的這種處理方式減少多余對(duì)象的生成秒裕,減少了內(nèi)存暫用,減少了GC钞啸,仔細(xì)想想還是挺贊的几蜻。(好像扯遠(yuǎn)了喇潘,該回歸正題想想怎么處理了o(╯□╰)o....)
一開始想了想,覺(jué)得可以這樣處理:在離開這個(gè)頁(yè)面時(shí)梭稚,記錄一下透明度颖低,把Drawable背景圖設(shè)置為完全可見的;回到這個(gè)頁(yè)面時(shí)弧烤,根據(jù)記錄值還原Drawable的透明度忱屑。
理想是美好的,現(xiàn)實(shí)是殘酷的暇昂。這個(gè)頁(yè)面的管理器是一個(gè)Fragment莺戒,筆者在onStop方法里做了上述這種處理,但還是會(huì)偶發(fā)出現(xiàn)這種透明度的Bug急波,看來(lái)筆者還有很多細(xì)節(jié)沒(méi)有考慮到(怎么感覺(jué)這細(xì)節(jié)越陷越深了/(ㄒoㄒ)/~~)从铲。
最終的解決辦法:
筆者無(wú)奈之下放棄了之前的處理方式,想了一個(gè)投機(jī)取巧的辦法:
把資源文件 lcs_actionbar_bg.png 又復(fù)制了一份,命名為lcs_actionbar_bg2.png
讓 lcs_actionbar_bg2.png 這張圖片獨(dú)立為這個(gè)導(dǎo)航欄漸變的頁(yè)面服務(wù)澄暮,不會(huì)影響到其他界面名段。
最后,華為手機(jī)導(dǎo)航欄透明度的Bug終于解決了泣懊。
題外話
有朋友問(wèn):搞個(gè)標(biāo)題欄特效為什么不用 AppBarLayout + CollapsingToolbarLayou + Toolbar 做個(gè)折疊效果伸辟?這個(gè)應(yīng)該很簡(jiǎn)單吧?
答:產(chǎn)品說(shuō)我們的App很多頁(yè)面都有這種效果馍刮,還是換種方式實(shí)現(xiàn)吧...
最后發(fā)表一下感慨: