简体   繁体   中英

Android ViewPager Not showing the right fragment

I am currently trying to implement a simple ViewPager that switches through fragments which are triggered in various ways (not by swiping). My app crashes when it tries to go from the 2nd fragment to the 3rd because it is actually skipping the GroupActivityFragment and tries to go to the GroupFrequencyFragment for some odd reason. I've tried many different tactics but this ViewPager systems seems to be so convoluted and buggy if don't want to use the swiping traversal.

Here is the activity holding the ViewPager

    public class CreateGroupActivity extends FragmentActivity {

    public static ViewPager viewPager;
    private CreateGroupPagerAdapter pagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_group);

        viewPager = findViewById(R.id.create_group_view_pager);
        pagerAdapter = new CreateGroupPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pagerAdapter);

        //Prevents swiping to next step.
        viewPager.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });


    }

    @Override
    public void onBackPressed() {
        if (viewPager.getCurrentItem() == 0) {
            // If the user is currently looking at the first step, allow the system to handle the
            // Back button. This calls finish() on this activity and pops the back stack.
            super.onBackPressed();
        } else {
            // Otherwise, select the previous step.
            viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
        }
    }
}

Here is the custom pager adapter used to create the different fragments

public class CreateGroupPagerAdapter extends FragmentStatePagerAdapter {
    private static final String TAG = "CreateGroupPagerAdapter";
    private static final int NUM_PAGES = 6;

    public CreateGroupPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        Log.i(TAG, "getItem: pos = " + position);
        Fragment fragment = null;
        switch (position) {
            case 0:
                fragment = new GroupFormatFragment();
                break;
            case 1:
                fragment = new GroupStyleFragment();
                break;
            case 2:
                fragment = new GroupActivityFragment();
                break;
            case 3:
                fragment = new GroupFrequencyFragment();
                break;
            case 4:
                fragment = new GroupSettingsFragment();
                break;
            case 5:
                fragment = new GroupInviteFriendsFragment();
                break;
        }

//        position = position + 1;

        return fragment;
    }


    // this counts total number of tabs
    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

Also all the Fragments inherit from an abstract class which has the method used to tell the viewpager to switch fragments.

   public abstract class GroupCreationFragment extends Fragment {
    public ArrayList<String> itemTitles;
    public ArrayList<String> itemDescriptions;
    private static final String TAG = "GroupCreationFragment";

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


    @Override
    public abstract View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

    /**
     * Sets the title of the fragment and then the filling content views.
     *
     * @param view
     */
    public abstract void setFragmentViews(View view);

    /**
     * Tell the parent activity's ViewPager to set the current item.
     *
     */
    public void notifyPageChange() {
        CreateGroupActivity.viewPager.setCurrentItem(CreateGroupActivity.viewPager.getCurrentItem()+1,true);
    }

}

Here is the GroupActivityFragment class which is being skipped.

   public class GroupActivityFragment extends GroupCreationFragment {


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


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_group_activity, container, false);
        itemTitles = new ArrayList<>();
        itemDescriptions = new ArrayList<>();
        setFragmentViews(view);
        return view;
    }

    @Override
    public void setFragmentViews(View view){
        itemTitles.add(getResources().getString(R.string.exercise_regularly));
        itemDescriptions.add(getResources().getString(R.string.exercise_regularly_desc));

        initRecyclerView(view);
    }

    private void initRecyclerView(View view) {
        RecyclerView recyclerView = view.findViewById(R.id.group_activity_recycler_view);
        GroupCreationRecyclerViewAdapter adapter = new GroupCreationRecyclerViewAdapter(getContext(), this, itemTitles, itemDescriptions, 2);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    }

}

And also the GroupCreationRecyclerViewAdapter class that handles the fragment change in the first 3 fragments.

   public class GroupCreationRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final String TAG = "GCRecyclerViewAdapter";
    private GroupCreationFragment parentFragment;
    private int pagePosition;

    public ArrayList<String> itemTitles = new ArrayList<>();
    public ArrayList<String> itemDescriptions = new ArrayList<>();
    private Context context;

    public GroupCreationRecyclerViewAdapter(Context context, GroupCreationFragment parentFragment, ArrayList<String> itemTitles, ArrayList<String> itemDescriptions, int pagePosition) {
        this.itemTitles = itemTitles;
        this.itemDescriptions = itemDescriptions;
        this.context = context;
        this.parentFragment = parentFragment;
        this.pagePosition = pagePosition;
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_group_creation_item, viewGroup, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
        ((ViewHolder) viewHolder).itemTitle.setText(itemTitles.get(i));
        ((ViewHolder) viewHolder).itemDescription.setText(itemDescriptions.get(i));
        final int pos = i;
        ((ViewHolder) viewHolder).parentLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Handle option choice.
                handleItemOnClick(itemTitles.get(pos));
            }
        });

    }

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

    public void handleItemOnClick(String title) {
        switch (title) {
            case "Private Group":
                GroupBuilder.getInstance().setFormat("private");
                break;
            case "Public Group":
                break;
            case "Accountability":
                GroupBuilder.getInstance().setStyle("accountability");
                break;
            case "Competition":
                break;
            case "Exercise Regularly":
                GroupBuilder.getInstance().setActivityType("exercise regularly");
                break;
        }

        Log.i("GroupCreationRV", "handleItemOnClick: " + GroupBuilder.getInstance().toString());
        Log.i(TAG, "notifyPageChange: getPagePosition = " + getPagePosition());
        // Tell ViewPager to move to next page.
        parentFragment.notifyPageChange();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView itemTitle;

        public TextView itemDescription;
        public ConstraintLayout parentLayout;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            itemTitle = itemView.findViewById(R.id.item_main_title);
            itemDescription = itemView.findViewById(R.id.item_description);
            parentLayout = itemView.findViewById(R.id.group_creation_item_layout);
        }

    }

    public int getPagePosition() {
        return pagePosition;
    }
}

Since the ViewPager creates a fragment ahead of the one being viewed, the GroupFrequencyFragment was being created at the same time as the one preceding it. This prosed a problem since a variable needed to be populated in order to showcase it properly on that fragment. So I implemented a way to delay the full initialization of the particular view until the fragment was being viewed by the user.

public class GroupFrequencyFragment extends GroupCreationFragment {

    private static final String TAG = "GroupFrequencyFragment";
    private static TextView activityLabel;
    public GroupFrequencyFragment() {
        // Required empty public constructor
    }


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

        setFragmentViews(view);
        return view;
    }

    @Override
    public void setFragmentViews(View view) {
        initFrequencyViews(view);
        setActivityLabel();
    }

    public void setActivityLabel() {
        if (GroupBuilder.getInstance().getActivityType() != null) {
            String type = GroupBuilder.getInstance().getActivityType();
            switch (type) {
                case "exercise regularly":
                    // Set the right label
                    Log.i(TAG, "setActivityLabel: activityLabel = " + activityLabel);
                    activityLabel.setText("workouts every");
                    break;
                default:
                    break;
            }
        }
    }

    private void initFrequencyViews(View view) {
        activityLabel = view.findViewById(R.id.activity_label);
        ...
    }

Here is the onPageListener for the ViewPager to wait for the fragment to be viewed.

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_group);

        viewPager = findViewById(R.id.create_group_view_pager);
        pagerAdapter = new CreateGroupPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pagerAdapter);

        //Prevents swiping to next step.
        viewPager.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });

        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i1) {

            }

            @Override
            public void onPageSelected(int i) {
                if (i == 3) {
                    CreateGroupPagerAdapter adapter = (CreateGroupPagerAdapter) viewPager.getAdapter();
                    GroupFrequencyFragment frequencyFragment = (GroupFrequencyFragment) adapter.getItem(3);
                    frequencyFragment.setActivityLabel();

                }
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });


    }

    @Override
    public void onBackPressed() {
        if (viewPager.getCurrentItem() == 0) {
            // If the user is currently looking at the first step, allow the system to handle the
            // Back button. This calls finish() on this activity and pops the back stack.
            super.onBackPressed();
        } else {
            // Otherwise, select the previous step.
            viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
        }
    }

ViewPager 's default configuration includes an optimization to eagerly create "off-screen" pages. The idea is that inflating one page to "either side" of the currently-visible page will help with smooth transitions as the user swipes between pages.

https://developer.android.com/reference/android/support/v4/view/ViewPager#setoffscreenpagelimit

Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. [...] This setting defaults to 1.

I see from your code that GroupActivityFragment is at index 2 and GroupFrequencyFragment is at index 3. So when you navigate to index 2, both GroupActivityFragment and GroupFrequencyFragment will be created at the same time.

Unfortunately, you can't set this number to less than 1. From the source code :

 if (limit < DEFAULT_OFFSCREEN_PAGES) { Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES); limit = DEFAULT_OFFSCREEN_PAGES; } 

This means that you will have to either move GroupFrequencyFragment "far enough" away from GroupActivityFragment so that it isn't created at the same time, or re-work your implementation so that being created at the same time isn't a problem.

Alternatively, you could stop using ViewPager altogether. If you don't want the user to be able to swipe between pages, why not simply use a FragmentTransaction and replace() your fragments as necessary?

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