简体   繁体   中英

Android TabLayout with ViewPager duplicates fragment contents when rotating

My app has a tablayout with a view pager. Each page has a fragment. There are 4 different fragments, three of them are basically the same for now (I'm in the development phase right now). One of them has a RecyclerView with a basic list. I am implementing the Two-pane template in the fragment with the RecyclerView. Everything seems to be works]ing well. While I move across the tabs the fragments are loaded fine. But, when I rotate the device and tap on the first tab, and then go back to the one with the recyclerview, I can see the previous intance below. See attached images. 票证中描述的错误 I decided to use static final instances of the fragments in the page adapter and in the recyclerview fragment. How can I get rid of this problem? Thanks in advance stackers!

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        TabLayout tabLayout = findViewById(R.id.tab_layout);
        tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon1).setText(R.string.dashboard));
        tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon2).setText(R.string.fragment2));
        tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon3).setText(R.string.fragmentDualPane));
        tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon4).setText(R.string.frag4));

        final ViewPager viewPager = findViewById(R.id.pager);
        final PagerAdapter pageAdapter = new TabPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pageAdapter);
        tabLayout.setupWithViewPager(viewPager);
    } // protected void onCreate

} // public class MainActivity

TabPagerAdapter has static final intances of the fragments

public class TabPagerAdapter extends FragmentPagerAdapter {
    static final Fragment tabs[] = {new DashboardFragment(),
                       new Fragment2(),
                       new ExpensesFragment(),
                       new Fragment4()
    };

    public TabPagerAdapter(@NonNull FragmentManager fm) {
        super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    } // public TabPagerAdapter

    @NonNull
    @Override
    public Fragment getItem(int position) {
        if (position<tabs.length)
            return tabs[position];
        else
            return null;
    } // public Fragment getItem

    @Override
    public int getCount() {
        return this.tabs.length;
    } // public int getCount

} // class TabPagerAdapter

General fragment template for dashboard, fragment2, and fragment4

public class DashboardFragment extends Fragment {
    public DashboardFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    } // onCreate

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_dashboard, container, false);
    }
}

This is the code for the fragment with the dual pane. Look that it uses the OnItemSelected implementation of fragments communications. This fragment loads another fragment with the recyclerview.

public class ExpensesFragment extends Fragment
        implements IOnItemSelected {

    @Override
    public void onAccountSelected(Account item) {
        System.out.println("Clicking on " + item.getTitle() + ", and isTwoPane=" + isTwoPane);
    } // public void onAccountSelected

    public static final String TAG="Expenses Fragment";

    private boolean isTwoPane = false; // Let's assume we're on a phone
    private FragmentManager fragmentManager;
    private View fragmentView = null;

    public ExpensesFragment() {
        // Required empty public constructor
    } // ExpensesFragment()

    public static final ExpensesListFragment lListFragment = new ExpensesListFragment();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    } // onCreate

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        fragmentView = inflater.inflate(R.layout.fragment_expenses, container, false);
        isTwoPane = fragmentView.findViewById(R.id.expensesDetailContainer) != null;
        fragmentManager = getChildFragmentManager();
        if (savedInstanceState==null) {
            if ( !lListFragment.isAdded() ) {
                fragmentManager.
                        beginTransaction().
                        add(R.id.expensesListContainer,lListFragment).
                        commit();
            } // if ( !lListFragment.isAdded() )
        } // if (savedInstanceState==null)

        if ( isTwoPane ) {
            fragmentManager.
                    beginTransaction().
                    replace(R.id.expensesDetailContainer,new EmptyFragment()).
                    commit();
        } // if ( isTwoPane )
        return fragmentView;
    } // onCreateView
} // ExpensesFragment

And this is the fragment with the recyclerview:

public class ExpensesListFragment extends Fragment {
    private IOnItemSelected mCallback;

    private RecyclerView rv;
    private RecyclerView.LayoutManager rvlm;
    private RecyclerAdapterAccounts rva;

    public ExpensesListFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCallback = (IOnItemSelected)getParentFragment();
    } // onCreate

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View fragmentView = inflater.inflate(R.layout.fragment_expenses_list, container, false);

        if ( isVisible() ) return fragmentView;

        FragmentManager fragmentManager = getChildFragmentManager();
        // Setting the recyclerview environment
        rv = fragmentView.findViewById(R.id.expensesRV); // recycler view
        rvlm = new LinearLayoutManager(getActivity());
        rv.setLayoutManager(rvlm);
        rva = new RecyclerAdapterAccounts();
        rva.setCallBackFunction(mCallback);
        rv.setAdapter(rva);
        // Setting the floating action button and snackbar
        FloatingActionButton fab = fragmentView.findViewById(R.id.fabAdd);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Load a Create Item frag", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
        return fragmentView;
    } // onCreateView
} // public class ExpensesListFragment

The RecyclerAdapterAccounts creates a generic set of data:

public class RecyclerAdapterAccounts extends
        RecyclerView.Adapter<RecyclerAdapterAccounts.ViewHolderAccounts> {
    private IOnItemSelected callBackFunction;
    public IOnItemSelected getCallBackFunction() {return callBackFunction;}
    public void setCallBackFunction(IOnItemSelected callBackFunction) {this.callBackFunction = callBackFunction;}

    class ViewHolderAccounts extends RecyclerView.ViewHolder {
        ImageView icon, isRepeating, isAlert;
        TextView title, total;

        public Account getAccount() {return account;}
        public void setAccount(Account account) {this.account = account;}
        Account account;

        public ViewHolderAccounts(View itemView) {
            super(itemView);
            icon = itemView.findViewById(R.id.list_item_ico_account);
            isRepeating = itemView.findViewById(R.id.list_item_isrepeating);
            isAlert = itemView.findViewById(R.id.list_item_isalert);
            title = itemView.findViewById(R.id.list_item_title_account);
            total = itemView.findViewById(R.id.list_item_desc_account);
            account = null; // The account needs to be set using the setter/getter method
        } // ViewHolderAccounts
    } // class ViewHolderAccounts

    List<Account> accts = new ArrayList<Account>();
    ViewGroup parent;

    @NonNull
    @Override
    public ViewHolderAccounts onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_account,parent,false);
        this.parent = parent;
        ViewHolderAccounts vh = new ViewHolderAccounts(v);
        return vh;
    } // onCreateViewHolder

    @Override
    public void onBindViewHolder(@NonNull ViewHolderAccounts holder, int position) {
        // Look into the list the item with id=position
        Optional<Account> la = accts.stream()
                .filter(ac->ac.getId()==(long)position)
                .findFirst();
        if ( la.isPresent() ) {
            int res =  parent.getResources().getIdentifier(la.get().getIcon(), "drawable", "com.almonisolutions.elgddt");
            holder.icon.setImageResource(res);
            holder.isRepeating.setImageResource(R.drawable.automatic);
            holder.isAlert.setImageResource(R.drawable.notifications);
            holder.title.setText(la.get().getTitle());
            holder.total.setText(la.get().getDescription());
            holder.setAccount(la.get());
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (holder.getAccount() != null) {
                        callBackFunction.onAccountSelected(holder.getAccount());
                    } // if (account != null)
                } // public void onClick
            });
        } // if
    } // onBindViewHolder


    @Override
    public int getItemCount() {
        return accts.size();
    } // getItemCount

    RecyclerAdapterAccounts() {
        super();
        for(int i=0;i<16;i++) {
            Account la = new Account();
            la.setId((long) i);
            la.setTitle("The item number " + i);
            la.setDescription("$" + (1000*i));
            switch(i%3) {
                case 0: la.setIcon("imaged"); break;
                case 1: la.setIcon("person_old"); break;
                case 2: la.setIcon("pet"); break;
                default: la.setIcon("add");
            } // switch
            accts.add(la);
        } // for
    } // RecyclerAdapterAccounts

} // class RecyclerAdapterAccounts

At first, In the ExpensesFragment I was getting an Exception that throw the message "Fragment already added". When I changed the ExpensesListFragment to static final, that error was gone.

Again, to recreate the error, you need to run in portrait mode, move through the tabs. Finish on anyone but the first one. Them rotate the device. Tap on the first tab. Then tap on the 3rd one, the one with the recyclerview. Swipe through the list and you will see it is double.

Any help will be appreciated. Thanks in advance!!!

So I found the answer. ADM (see comment above) sent me to a previous article where part of the solution was to extend ViewPager and override instantiateItem. But I did not want to extend ViewPager. However, in the same article was another link to this other article where there was the following explanation:

Blockquote By default, [FragmentPagerAdapter] will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter).

So, I made TabPagerAdapter extend FragmentStatePagerAdapter instead of FragmentPageAdapter... and that was it!!

Thanks ADM for pointing to the right series of articles.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM