一、需求
在viewpager+fragment+tablayout中根據權限動態設置顯示/隱藏某個tab。
二、背景
一個問題斷斷續續解決了好幾天,明明感覺很簡單的需求,就是會遇到各種問題,而且錯誤都能在源碼中看到,實在是解決太久了,人煩了,照搬網上的實現,但是每個人遇到的問題不一定一樣,還是要根據實際情況來定。
這個問題過程中遇到很多錯誤。
- fragment布局不顯示。
可能原因:fragment被添加多次,但是已經在activity中繪制過了。
解決方法:判斷fragment是否被重復添加
2. fragment移除時,發生下標越界異常。
原因:因為在移除item后,執行了notifysetChanged方法。而在其中調用了tab的監聽。我在監聽事件中做了tab選中/不選中的圖標設置。
解決方法:
在添加/移除前移除tab的監聽,另外,在adapter刷新后再將tab添加回來。
三、需求
以下是最終實現的代碼:
1. 代碼初始化
override fun initView() { tabPagerAdapter = TabPagerAdapter(supportFragmentManager) mViewPager.adapter = tabPagerAdapter?.apply { //fragmentList
setFragments(tabFragments) } mViewPager.offscreenPageLimit = MAX_FRAGMENT_SIZE mTabLayout.setupWithViewPager(mViewPager) updateTab(0) tabLister = object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { setImageState(mTabLayout.selectedTabPosition) setDarkModeStatus(mTabLayout.selectedTabPosition) } override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} }?.apply { mTabLayout.addOnTabSelectedListener(this) } }
tab.setupWithViewpager就是和viewpager進行綁定。
private void setupWithViewPager( @Nullable final ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) { if (this.viewPager != null) { // If we've already been setup with a ViewPager, remove us from it
if (pageChangeListener != null) { this.viewPager.removeOnPageChangeListener(pageChangeListener); } if (adapterChangeListener != null) { this.viewPager.removeOnAdapterChangeListener(adapterChangeListener); } } if (currentVpSelectedListener != null) { // If we already have a tab selected listener for the ViewPager, remove it
removeOnTabSelectedListener(currentVpSelectedListener); currentVpSelectedListener = null; } if (viewPager != null) { //與viewpager進行綁定
this.viewPager = viewPager; // Add our custom OnPageChangeListener to the ViewPager
if (pageChangeListener == null) { pageChangeListener = new TabLayoutOnPageChangeListener(this); } pageChangeListener.reset(); viewPager.addOnPageChangeListener(pageChangeListener); // Now we'll add a tab selected listener to set ViewPager's current item //添加tab選擇默認監聽
currentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); addOnTabSelectedListener(currentVpSelectedListener); final PagerAdapter adapter = viewPager.getAdapter(); if (adapter != null) { //添加一個監聽,adapter發生改變時,tab更新
setPagerAdapter(adapter, autoRefresh); }
void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) { if (pagerAdapter != null && pagerAdapterObserver != null) { // If we already have a PagerAdapter, unregister our observer
pagerAdapter.unregisterDataSetObserver(pagerAdapterObserver); } pagerAdapter = adapter; if (addObserver && adapter != null) { // Register our observer on the new adapter
if (pagerAdapterObserver == null) { pagerAdapterObserver = new PagerAdapterObserver(); } adapter.registerDataSetObserver(pagerAdapterObserver); } // Finally make sure we reflect the new adapter
populateFromPagerAdapter(); }
void populateFromPagerAdapter() { removeAllTabs(); if (mPagerAdapter != null) { final int adapterCount = mPagerAdapter.getCount(); for (int i = 0; i < adapterCount; i++) { addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); } // Make sure we reflect the currently set ViewPager item
if (mViewPager != null && adapterCount > 0) { final int curItem = mViewPager.getCurrentItem(); if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { selectTab(getTabAt(curItem)); } } } }
當執行pageAdapter的notifyDataSetchanged時
public void notifyDataSetChanged() { synchronized (this) { if (mViewPagerObserver != null) { mViewPagerObserver.onChanged(); } } //onChanged()方法會回調所有Observers者的onChanged方法,最終執行populateFromPagerAdapter方法進行tab的更新
mObservable.notifyChanged(); }
錯誤2報錯的原因
//tablayout.setUpWithViewpager()會設置pageSelect監聽,設置viewPager的選擇時回調監聽,進而回調tab的onSelected方法,這時 //notify方法會有延遲,因此此時tab的count也沒刷新,而走進循環后,更新導致的報錯
public void onPageSelected(final int position) { final TabLayout tabLayout = tabLayoutRef.get(); if (tabLayout != null
&& tabLayout.getSelectedTabPosition() != position && position < tabLayout.getTabCount()) { // Select the tab, only updating the indicator if we're not being dragged/settled // (since onPageScrolled will handle that).
final boolean updateIndicator = scrollState == SCROLL_STATE_IDLE || (scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE); tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); } }
2.adapter的實現
open class TabPagerAdapter(var fm: FragmentManager) : FragmentPagerAdapter(fm){ private var fragments: MutableList<BaseTabLazyFragment> = mutableListOf() private var count = 10
private var mCurTransaction:FragmentTransaction? = null
private var tags= mutableListOf<String>() fun clearFragments(){ fragments.clear() notifyDataSetChanged() } fun setFragments(frags: List<BaseTabLazyFragment>) { fragments.addAll(frags) notifyDataSetChanged() } fun addFragments(index:Int,frags: BaseTabLazyFragment) { fragments.add(index,frags) notifyDataSetChanged() } fun removeFragments(frags: BaseTabLazyFragment){ fragments.remove(frags) notifyDataSetChanged() } override fun getItemPosition(obj : Any): Int = POSITION_NONE override fun getItem(position: Int): Fragment = fragments[position] override fun getItemId(position: Int): Long { var fragment = fragments[position] return when(fragment.pageName){ "DeviceFragment"-> 1.toLong() "AuthorizationTabFragment" -> 2.toLong() "MonitorMessageTabFragment" -> 3.toLong() "MonitorSettingTabFragment" -> 4.toLong() else -> super.getItemId(position) } } override fun getCount(): Int = fragments.size
這里注意要重寫getItemId,否則移除N遍也不會成功。
public Object instantiateItem(@NonNull ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId); //優先獲取tag中的fragment,沒有的話才創建。而itemId默認是position,移除某個fragment是移除不成功的,會在 // fragmentManager中存儲添加過的所有fragment
Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED); } else { fragment.setUserVisibleHint(false); } } return fragment; }
四、使用
fun addFragment(index: Int, fragment: BaseTabLazyFragment) { if (tabFragments.size >= MAX_FRAGMENT_SIZE) return tabLister?.let { mTabLayout.removeOnTabSelectedListener(it) } var curItem = mViewPager.currentItem tabFragments.add(index, fragment) tabPagerAdapter?.addFragments(index, fragment) setCurTab(if (curItem >= index) curItem + index else curItem) } fun removeFragment(index: Int, fragment: BaseTabLazyFragment) { if (tabFragments.size < MAX_FRAGMENT_SIZE) return tabLister?.let { mTabLayout.removeOnTabSelectedListener(it) } var curItem = mViewPager.currentItem tabFragments.remove(fragment) tabPagerAdapter?.removeFragments(fragment) setCurTab(if (curItem >= index) curItem - index else curItem) } fun setCurTab(curItem: Int) { mViewPager.currentItem = curItem updateTab(curItem) tabLister?.let { mTabLayout.addOnTabSelectedListener(it) } }
