ViewPager+Fragment实现Tab动态添加和移除


一、需求

  在viewpager+fragment+tablayout中根据权限动态设置显示/隐藏某个tab。

二、背景

 一个问题断断续续解决了好几天,明明感觉很简单的需求,就是会遇到各种问题,而且错误都能在源码中看到,实在是解决太久了,人烦了,照搬网上的实现,但是每个人遇到的问题不一定一样,还是要根据实际情况来定。 这个问题过程中遇到很多错误。
  1. 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 = mutableListOf()
    private var count = 10
    private var mCurTransaction:FragmentTransaction? = null
    private var tags= mutableListOf()

    fun clearFragments(){
        fragments.clear()
        notifyDataSetChanged()
    }

    fun setFragments(frags: List) {
        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) }
}