TabLayout scrolls to unkown position after calling notifyDataSetChanged on PagerAdapter

后端 未结 5 1187
死守一世寂寞
死守一世寂寞 2021-02-14 19:23

I have sample project with TabLayout and PagerAdapter. Strange things happens with TabLayout when I call pagerAdapter.notifyDataSetChanged();

相关标签:
5条回答
  • 2021-02-14 19:46

    Every time setup view pager with the use of setupWithViewPager might be costly. Because notifyDataSetChanged() on your ViewPager Adapter causing TabLayout to redraw its all views. So for redrawing, TabLayout remove all its associated Views and re add them.

    Please check below thread execution steps which happen after notifyDataSetChanged on pager adapter.

      at android.support.design.widget.TabLayout.removeAllTabs(TabLayout.java:654)
      at android.support.design.widget.TabLayout.populateFromPagerAdapter(TabLayout.java:904)
      at android.support.design.widget.TabLayout$PagerAdapterObserver.onChanged(TabLayout.java:2211)
      at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
      - locked <0x129f> (a java.util.ArrayList)
      at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:287)
      at com.sample.testtablayout.MainActivity$1.onClick(MainActivity.java:45)
    

    According to thread execution -

    Clicked on Action Button > Pager Adapter Notified for data set changed > TabLayout got notification of data change through observers > It try to populate new data from adapter > Removed all tabs.

    Below is a code of TabLayout class, from which you can check whenever all tabs are going to remove then last selected tab is lost(Intentionally marked null).

    /**
     * Remove all tabs from the action bar and deselect the current tab.
     */
    public void removeAllTabs() {
        // Remove all the views
        for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) {
            removeTabViewAt(i);
        }
    
        for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) {
            final Tab tab = i.next();
            i.remove();
            tab.reset();
            sTabPool.release(tab);
        }
    
        mSelectedTab = null; // Thats a cause for your issue.
    }
    

    To retain last selected tab I have created my own CustomTabLayout class and retained last selected position.

    public class RetainableTabLayout extends TabLayout {
    
       /*
       * Variable to store invalid position.
       */
       private static final int INVALID_TAB_POS = -1;
    
       /*
       * Variable to store last selected position, init it with invalid tab position.
       */
       private int mLastSelectedTabPosition = INVALID_TAB_POS;
    
       public RetainableTabLayout(Context context) {
           super(context);
       }
    
       public RetainableTabLayout(Context context, AttributeSet attrs) {
           super(context, attrs);
       }
    
       public RetainableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
      }
    
       @Override
        public void removeAllTabs() {
           // Retain last selected position before removing all tabs
           mLastSelectedTabPosition = getSelectedTabPosition();
           super.removeAllTabs();
       }
    
       @Override
       public int getSelectedTabPosition() {
           // Override selected tab position to return your last selected tab position
           final int selectedTabPositionAtParent = super.getSelectedTabPosition();
           return selectedTabPositionAtParent == INVALID_TAB_POS ? 
                  mLastSelectedTabPosition : selectedTabPositionAtParent;
       }
    }
    

    At the end make sure to reselect your tab after recreation of your TabLayout.

    findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pagerAdapter.notifyDataSetChanged();
                // At the end make sure to reselect your last item.
                new Handler().postDelayed(
                        new Runnable() {
                            @Override
                            public void run() {
                                final TabLayout.Tab selectedTab = tabLayout.getTabAt(
                                     tabLayout.getSelectedTabPosition());
                                if (selectedTab != null) {
                                    selectedTab.select();
                                }
                            }
                        }, 100);
            }
        });
    

    This issue will resolve your problem but what I believe is TabLayout must have to retain last selected position in case of data set change. This is what I understand, any comments or more understanding is welcome.

    0 讨论(0)
  • 2021-02-14 19:53

    I just added notifyDataSetChanged method inside of TabLayout implementation of Rasi.

    It works for me.

    public class CustomTabLayout extends TabLayout {
    
    /*
       * Variable to store invalid position.
       */
    private static final int INVALID_TAB_POS = -1;
    
    /*
    * Variable to store last selected position, init it with invalid tab position.
    */
    private int mLastSelectedTabPosition = INVALID_TAB_POS;
    
    public CustomTabLayout(Context context) {
        super(context);
    }
    
    public CustomTabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @Override
    public void removeAllTabs() {
        // Retain last selected position before removing all tabs
        mLastSelectedTabPosition = getSelectedTabPosition();
        super.removeAllTabs();
    }
    
    @Override
    public int getSelectedTabPosition() {
        // Override selected tab position to return your last selected tab position
        final int selectedTabPositionAtParent = super.getSelectedTabPosition();
        return selectedTabPositionAtParent == INVALID_TAB_POS ?
                mLastSelectedTabPosition : selectedTabPositionAtParent;
    }
    
    public void notifyDataSetChanged() {
        post(new Runnable() {
            @Override
            public void run() {
                TabLayout.Tab selectedTab = getTabAt(getSelectedTabPosition());
                if (selectedTab != null) {
                    selectedTab.select();
                }
            }
        });
    }
    }
    

    and to notify call

    mPagerAdapter.notifyDataSetChanged();
    mCustomTabLayout.notifyDataSetChanged();
    
    0 讨论(0)
  • 2021-02-14 19:54

    You can call:

    setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh)
    

    and set param autoRefresh to false.

    See also: TabLayout(ViewPager,boolean)

    0 讨论(0)
  • 2021-02-14 20:05

    you can directly set the current postion.

    you can get the selected position before notifying dataset change and save it in static varaible and update it everytime:

    public static tabPostion;
    
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
          tabPostion = tab.getPosition();
        }
    
        @Override
        public void onTabUnselected(TabLayout.Tab tab) {
    
        }
    
        @Override
        public void onTabReselected(TabLayout.Tab tab) {
    
        }
    
    //set the postion of the pervious tab
        yourviewpager.setCurrentItem(tabPostion);
    

    below is the code from my project :

       @Override
        public void onTabSelected(TabLayout.Tab tab) {
    
            // imageHashMap = new HashMap<>();
            onClearOrSelectAllImage(FlickQuickEnum.PIC_SELECTION_ACTION.CLEAR);
            selectedImageUrls = new HashMap<>();
            String tag = tab.getText().toString();
            String tagName = tag.substring(1, tag.length());
            currentTab = tagName;
            if (tagName != null) {
                dbAlbumPhotoList = new ArrayList<>();
                if (tagName.equals("All")) {
                    dbAlbumPhotoList = dbAlbumPhotosHashMap.get(album.getAlbumName());
                } else {
                    dbAlbumPhotoList = dbAlbumPhotosHashMap.get(tagName);
                }
            }
            updatePhotoCount();
            setPhotosSelectedActions(false);
        }
    
        @Override
        public void onTabUnselected(TabLayout.Tab tab) {
    
        }
    
        @Override
        public void onTabReselected(TabLayout.Tab tab) {
    
        }
    
    0 讨论(0)
  • 2021-02-14 20:08

    I fix the issue, you need modify the TabLayout source code

    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);
            }
    
            // need call post to run the code, to fix children views not layout
            post(new Runnable() {
                @Override
                public void run() {
                    // 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));
                        }
                    }
                }
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题