Android TabLayout在與viewpager AppBarLayout一起使用時出現tab選中後下劃線滑動緩慢,卡頓異常解決方案
今天早上剛測試發現的一個問題,之前沒有注意到,特別尷尬感覺,之前經常使用TabLayout和viewpager聯動切換碎片,異常的情況如下圖展示:
佈局程式碼如下:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#383d4d" android:orientation="vertical"> <android.support.design.widget.AppBarLayout android:id="@+id/fragment_ring_appbarlayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <
[email protected]/dp_109--> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/ll_ring_top_view" android:layout_width="match_parent" android:layout_height="@dimen/dp_60" android:background="#282c38" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:statusBarScrim="#282c38"> <RelativeLayout android:id="@+id/rl_ring_title" android:layout_width="match_parent" android:layout_height="@dimen/dp_60" android:background="#282c38"> <com.blossom.ripple.widget.AvenirNextRegularTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Ring" android:textSize="@dimen/sp_16" android:textColor="@color/white" android:layout_marginTop="@dimen/dp_28" android:layout_centerHorizontal="true"/> </RelativeLayout> </android.support.design.widget.CollapsingToolbarLayout> <LinearLayout android:id="@+id/ring_tab_outside" android:layout_width="match_parent" android:layout_height="@dimen/dp_49" android:layout_gravity="bottom" android:background="#282c38" android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="@dimen/dp_1" android:background="#353944"/> <android.support.design.widget.TabLayout android:id="@+id/tablayout_top_ring" android:layout_width="match_parent" android:layout_height="@dimen/dp_48" android:paddingLeft="@dimen/dp_3" android:paddingRight="@dimen/dp_3" app:tabIndicatorColor="@color/white" app:tabTextColor="@color/white" app:tabMode="fixed"> </android.support.design.widget.TabLayout> </LinearLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.view.ViewPager android:id="@+id/vp_ring" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> </android.support.v4.view.ViewPager> </android.support.design.widget.CoordinatorLayout>
fragment中的程式碼如下:
override fun initView(inflater: LayoutInflater): View { var view = inflater.inflate(R.layout.fragment_ring,null) initFindViewById(view ) return view } fun initFindViewById(view: View) { rl_ring_title = view.rl_ring_title view.tablayout_top_ring.addTab(view.tablayout_top_ring.newTab()) view.tablayout_top_ring.addTab(view.tablayout_top_ring.newTab()) view.tablayout_top_ring.setupWithViewPager(view.vp_ring) var ringListFragment1Bundle = Bundle() ringListFragment1Bundle.putSerializable("ringType",1) var ringListFragment1 = RingListFragment() ringListFragment1.arguments = ringListFragment1Bundle var ringListFragment2Bundle = Bundle() ringListFragment2Bundle.putSerializable("ringType",2) var ringListFragment2 = RingListFragment() ringListFragment2.arguments = ringListFragment2Bundle list!!.add(ringListFragment1) list!!.add(ringListFragment2) var adapter = CustomTabPagerAdapter(childFragmentManager,list,context,null) view.vp_ring.adapter = adapter for (i in 0 until adapter.count){ val tab = view?.tablayout_top_ring?.getTabAt(i)//獲得每一個tab if(i==0){ tab?.setCustomView(R.layout.tab_ringtones_item)//給每一個tab設定view }else{ tab?.setCustomView(R.layout.tab_notification_item)//給每一個tab設定view } } var offset = view.ll_ring_top_view.layoutParams.height view.fragment_ring_appbarlayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener{ override fun onOffsetChanged(p0: AppBarLayout?, verticalOffset: Int) { Log.d(TAG, "OffsetChangedListener-------verticalOffset==$verticalOffset") view!!.rl_ring_title.alpha = (offset!!.toFloat() - (Math.abs(verticalOffset).toFloat())).div(offset!!.toFloat()) if (Math.abs(verticalOffset)>=offset){ var lp = view!!.ring_tab_outside.layoutParams lp.height = DipDpUtil.dip2px(activity,75f) view.ring_tab_outside.layoutParams = lp var lp2 = view!!.tablayout_top_ring.layoutParams lp2.height = DipDpUtil.dip2px(activity,74f) view.tablayout_top_ring.layoutParams = lp2 view.tablayout_top_ring.invalidate() }else{ var lp = view!!.ring_tab_outside.layoutParams lp.height = DipDpUtil.dip2px(activity,49f) view.ring_tab_outside.layoutParams = lp var lp2 = view!!.tablayout_top_ring.layoutParams lp2.height = DipDpUtil.dip2px(activity,48f) view.tablayout_top_ring.layoutParams = lp2 view.tablayout_top_ring.invalidate() } } }) }
程式碼都貼上了,下面來分享我解決問題的過程和思路,剛開始分析確定了兩個可能:
1.是TabLayout這個控制元件使用時程式碼呼叫或者屬性設定出了問題;
2.TabLayout放在CoordinatorLayout和AppBarLayout中出現了載入和滑動的異常
然後開始逐步開始嘗試找解決方案,逐步排除可能
我試著在xml佈局中找TabLayout中與tabIndicator相關的各個屬性設定,然後發現了 app:tabIndicatorAnimationDuration=""我試著將他設定為70,然後再執行測試,發現雖然tab切換時導航條下劃線滑動延遲稍微優化了一點,但還是會有出現,看來這個不是解決的最佳辦法,然後發現沒有別的和tabIndicator滑動卡頓或速度相關的屬性了,於是開始分析第二種可能性,因為產品要求必須要有頂部標題欄向上滑動消失,tablayout置頂的效果(要求的效果如下圖所示),
所以才加入了用TabLayout放在CoordinatorLayout和AppBarLayout中來實現,因為之前用recyclerview加setOnScrollListener來動態改變頂部導航的消失隱藏於透明度會出現滑動時閃和抖動的異常,當然這是題外話,繼續聊我們這個bug的解決思路,但是CoordinatorLayout和AppBarLayout的配合使用裡面的各種屬性比較複雜,我不知該從哪裡下手,後來我看我程式碼中appbarlayout.addOnOffsetChangedListener()方法時突然有了一個想法,因為之前發現給appbarlayout設定這個監聽的時候觀察過log日誌中的列印,這個滑動便宜的verticalOffset會一直不停的列印,不管你是否對他進行了滑動的操作,也就是說哪怕頁面是靜止狀態,它也會一直進行監聽操作,如下圖log列印gif圖
然後我開始想是不是我在這個監聽裡面動態改變tablayout高度的程式碼出現了問題,因為裡面的判斷是這樣寫的:
view.fragment_ring_appbarlayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener{
override fun onOffsetChanged(p0: AppBarLayout?, verticalOffset: Int) {
Log.d(TAG, "OffsetChangedListener-------verticalOffset==$verticalOffset")
view!!.rl_ring_title.alpha = (offset!!.toFloat() - (Math.abs(verticalOffset).toFloat())).div(offset!!.toFloat())
if (Math.abs(verticalOffset)>=offset){
var lp = view!!.ring_tab_outside.layoutParams
lp.height = DipDpUtil.dip2px(activity,75f)
view.ring_tab_outside.layoutParams = lp
var lp2 = view!!.tablayout_top_ring.layoutParams
lp2.height = DipDpUtil.dip2px(activity,74f)
view.tablayout_top_ring.layoutParams = lp2
view.tablayout_top_ring.invalidate()
}else{
var lp = view!!.ring_tab_outside.layoutParams
lp.height = DipDpUtil.dip2px(activity,49f)
view.ring_tab_outside.layoutParams = lp
var lp2 = view!!.tablayout_top_ring.layoutParams
lp2.height = DipDpUtil.dip2px(activity,48f)
view.tablayout_top_ring.layoutParams = lp2
view.tablayout_top_ring.invalidate()
}
}
})
這樣就會導致一個問題,當verticalOffset的絕對值大於等於定好的offset時改變tablayout的高度,但正常情況沒有進行操作的時候會走下面的判斷,設定正常的高度,並且會一直呼叫invalidate方法強制重新整理tablayout,這肯定會導致tablayout做很多沒有必要且損耗效能的操作,ok,這是個需要優化的地方,有可能就是這個疏忽造成的bug,於是改成下面的程式碼:
var offset = view.ll_ring_top_view.layoutParams.height
view.fragment_ring_appbarlayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener{
override fun onOffsetChanged(p0: AppBarLayout?, verticalOffset: Int) {
Log.d(TAG, "OffsetChangedListener-------verticalOffset==$verticalOffset")
view!!.rl_ring_title.alpha = (offset!!.toFloat() - (Math.abs(verticalOffset).toFloat())).div(offset!!.toFloat())
if (Math.abs(verticalOffset)>=offset){
if (!(view.ring_tab_outside.height ==DipDpUtil.dip2px(activity,75f) )){
var lp = view!!.ring_tab_outside.layoutParams
lp.height = DipDpUtil.dip2px(activity,75f)
view.ring_tab_outside.layoutParams = lp
}
if (!(view.tablayout_top_ring.height ==DipDpUtil.dip2px(activity,74f) )){
var lp2 = view!!.tablayout_top_ring.layoutParams
lp2.height = DipDpUtil.dip2px(activity,74f)
view.tablayout_top_ring.layoutParams = lp2
view.tablayout_top_ring.invalidate()
}
}else{
if (!(view.ring_tab_outside.height ==DipDpUtil.dip2px(activity,49f) )){
var lp = view!!.ring_tab_outside.layoutParams
lp.height = DipDpUtil.dip2px(activity,49f)
view.ring_tab_outside.layoutParams = lp
}
if (!(view.tablayout_top_ring.height ==DipDpUtil.dip2px(activity,48f) )){
var lp2 = view!!.tablayout_top_ring.layoutParams
lp2.height = DipDpUtil.dip2px(activity,48f)
view.tablayout_top_ring.layoutParams = lp2
view.tablayout_top_ring.invalidate()
}
}
}
})
再執行發現tablayout中的tab滑動切換,下面的導航條不再有滑動延遲,卡頓的情況,ok,這個bug造成的原因找到了,看來以後給appbarlayout新增addOnOffsetChangedListener監聽時,當中的操作一定要做好優化,否則會一直不斷的被呼叫,對app效能造成不必要的損耗和浪費,這次出現的這個滑動卡頓的bug就是這個原因造成的。