I am having strange issue in FragmentStatePagerAdapter.when i swipe front ,it works gud,when i swipe back it skip 2 fragments.how to resolve this?
is there any way to get current item no?
NavigationPagerAdapter
public static class NavigationPagerAdapter extends FragmentStatePagerAdapter {
public NavigationPagerAdapter(android.support.v4.app.FragmentManager fm ) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new NaviagtionFragment();
Bundle args = new Bundle();
args.putInt("position", i); // Our object is just an integer :-P
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
// For this contrived example, we have a 100-object collection.
return 100;
}
#Override
public CharSequence getPageTitle(int position) {
return "OBJECT " + (position );
}
in fragment
public static class NaviagtionFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.item_pager, container, false);
Bundle args = getArguments();
int m= args.getInt("position");
Toast.makeText(c, ""+m, Toast.LENGTH_SHORT).show();//here i tracked the position of fragments when i swipe front ,its increasing 1,2,3,4,5,6,7 but when i swipe back it will go directly 7 -5 th position..
}}}
Toast is very slow and it is not good solution when you are swiping views, because it remains on the screen longer and can not reach to show all messages, so its better to check position in LogCat. Try this for items position and see position with Log:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("position") : 1;
Log.i("tag", "Item clicked: " + mNum);
}
Related
I want to make a quiz application. So far I have 3 activities - home, quiz, score. Since the quiz activity contains multiple equivalent views ( image header, question and 4 answer buttons ), I did some reading and decided that
ViewPager with FragmentStatePagerAdapter show do the trick. So I made an xml template and inflated couple of test views and it was all looking good, until I started handling the user interaction.
I want to simulate a toggle button and there is only one correct answer to each question, so selecting one button should deselect the previous one ( if any ). When the button is pressed I change my Question model, then I find all 4 buttons with findViewById and reset their color filter. Then I set that filter back on my selected button. To determine which question model to update I use the current fragment position, which I have set ( using setTag, in fragment's onCreate ) in my template root view.
This is how I call my fragmets:
public Fragment getItem(int position) {
Question question = Repository.findById(position);
int correctAnswerBtnId;
switch (question.getCorrectAnswerIndex()) {
case 0: correctAnswerBtnId = R.id.quiz_answer_0_btn; break;
case 1: correctAnswerBtnId = R.id.quiz_answer_1_btn; break;
case 2: correctAnswerBtnId = R.id.quiz_answer_2_btn; break;
case 3: correctAnswerBtnId = R.id.quiz_answer_3_btn; break;
this.ACTIVITY_ROOT.setTag(question.getID());
Fragment fragment = new QuestionFragment();
Bundle args = new Bundle();
args.putSerializable(QuestionFragment.QUESTION, question);
fragment.setArguments(args);
return fragment;
}
My QuestionFragment onCreateView is as per documentation:
public View onCreateView(
LayoutInflater inflater,
ViewGroup container,
Bundle questionData) {
this.rootView = inflater.inflate(
R.layout.layout_question_template,
container,
false);
Bundle args = getArguments();
this.question = (Question) args.getSerializable(QuestionFragment.QUESTION);
populateInflatable();
rootView.findViewById(R.id.layout_question_template_root).setTag(this.question.getID());
return rootView;
}
In populateInflatable I use this.rootView to fintViewById and populate it with my question data. Then I change the color of a button, if there is selected one from the Question.
On button click I call selectAnserButton :
public void selectAnswerButton(View selectedButton) {
int questionId =
(int) this.activityRoot.findViewById(
R.id.layout_question_template_root).getTag(); //??
unSelectAllButtons();
changeColor(selectedButton);
Repository.findById(questionId).selectAnswer(selectedButton.getId());
}
Where unSelectAllButtons represents buttonToUnSelect.getBackground().clearColorFilter(); on the four buttons. and Repository is just a static class with example question data.
It all goes terribly wrong, when I have more then one view. On each fragment I inflate the same xml with same View IDs, as I have defined them. And as I now understand calling findViewById retrieves not one, but all views with that Id from my current, but also from my previous and next fragment as well. So every time I want to select my current fragment's view, I also modify the same view in the previous and next fragments as well. You can imagine how this is problematic. This makes me feel I have a fundamental mistake, because I don't think there is supposed to be more then one View with same ID.
I really don't understand how I should do this using ViewPager. At this point it feels like I'm trying to make a wood carving, but instead I am hacking the framework to pieces. There must be a better way to do this with ViewPager.
RESOLVED: Thanks to Soo Chun Jung for pointing me to the answer. In short what got it working for me was:
Passing my Question model id to each fragment with Bundle.
Storing each fragment in inside an ArrayMap with fragment position as key and fragment as value.
Getting each individual fragment from my selectAnswer function is now easy: first get the current fragment's position with myViewPager.getCurrentItem, then calling getter function which returns a fragment on the current position.
Now that I have the fragment I can easily change its button's because they are kept as private fields, assigned in the 'onCreateView` method.
Hope it's helpful~
adapter
class CustomAdapter extends FragmentPagerAdapter {
private final String[] TITLES = {"A", "B"};
private final String TAG = CustomAdapter.class.getSimpleName();
private final ArrayList<Fragment> mFragments;
private final FragmentManager fm;
public CustomAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<>(getCount());
this.fm = fm;
}
#Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
#Override
public int getCount() {
return TITLES.length;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem position = " + position);
mFragments.remove(object);
super.destroyItem(container, position, object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
mFragments.add((Fragment) object);
return object;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem position = " + position);
if (position == 0) {
return MyFragmentA.newInstance();
} else if (position == 1) {
return MyFragmentB.newInstance();
}
return null;
}
public MyFragmentA getMyFragmentA() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentA) {
return (MyFragmentA) f;
}
}
}
return null;
}
public MyFragmentB getMyFragmentB() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
return (MyFragmentB) f;
}
}
}
return null;
}
}
Fragment class
public class MyFragmentB extends Fragment {
...
public updateYourUI(){
//update something
}
}
Usage
mPager = (CustomViewPager) findViewById(R.id.pager);
mAdapter = new CustomAdapter(getChildFragmentManager());
mPager.setAdapter(mAdapter);
mAdapter.getMyFragmentB().updateYourUI();
for your comment below If you only have one kind Fragment. You can modify some function like this.
public static MyFragmentB newInstance(int ID) {
MyFragmentB fragment = new MyFragmentB();
Bundle bundle = new Bundle();
bundle.putInt("ID", ID);
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
myID = getArguments().getInt("ID");
....
}
public int getMyID() {
return myID;
}
public MyFragmentB getMyFragmentByID(String id) {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
MyFragmentB temp = (MyFragmentB)f;
if(temp.getID.equals(id){
return (MyFragmentB) f;
}
}
}
}
return null;
}
I am currently using Material Design in an Android app that I am making. In this app, I am using the Material Design tab layout to display some information that I am receiving. However when I tap the tabs, the animation is not smooth, and it is very abrupt. Sliding to go to the other tab, however is very smooth.
mTabLayout = (TabLayout) findViewById(R.id.chem_tab_layout);
mGenericAdapter = new GenericPagerAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.view_pager);
mPager.setAdapter(mGenericAdapter);
//Notice how the Tab Layout links with the Pager Adapter
mTabLayout.setTabsFromPagerAdapter(mGenericAdapter);
//Notice how The Tab Layout and View Pager object are linked
mTabLayout.setupWithViewPager(mPager);
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout){
#Override
public void onPageSelected(int position) {
mGenericAdapter.notifyDataSetChanged();
}
});
That is my code for setting the adapter, etc.
This is my custom adapter code for the tabs:
class GenericPagerAdapter extends FragmentStatePagerAdapter {
public GenericPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ChemGridActivity.MyFragment myFragment = new ChemGridActivity.MyFragment();
return myFragment;
}
#Override
public int getCount() {
return 3; //returns number of tabs that need to be created
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) return "Chemistry";
if (position == 1) return "Mathematics";
if (position == 2) return "Physics";
else return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
I feel that the choppy transition between tabs is caused by the overriden method onPageSelected method when I add onPageChangeListener. What do I add to this method to make tapping on tabs a smoother animation?
Without knowing much about the internals of your classes, I imagine the problem is not that you have a listener, but what you are doing inside that listener.
In the case of most adapters notifyDataSetChanged() will cause it to re-render the entire view again (including all pages).
Seeing as you haven't specified what the intent here with the notification is, it's hard to tell you how you can do this in an alternative way, but you do need to do something less intensive if you want the animation to remain smooth.
I suspect you just want to change which fragment is shown, in which case just use the FragmentManager where necessary, remembering to reuse fragments which have already been seen once.
EDIT Based on additional info in comments
#Override
public Fragment getItem(int position) {
//POSITION_SOMETHINHG would be one of a set of constants to indicate hwa to display
return ChemGridActivity.MyFragment.newInstance(ChemGridActivity.MyFragment.POSITION_SOMETHINHG);
}
public class ChemGridActivity.MyFragment ... {
private static final String KEY_DISPLAY_TYPE = "KEY_DISPLAY_TYPE";
public static final int POSITION_SOMETHINHG = 11111;
public static MyFragment newInstance(int display) {
MyFragment f = new MyFragment();
Bundle bund = new Bundle();
bund.putInt(KEY_DISPLAY_TYPE, display);
f.setArguments(bund);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mDisplay = args.getInt(KEY_DISPLAY_TYPE, 0);
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.my_layout, container, false);
//TODO: change something based on mDisplay
return view;
}
I have a FragmentStatePagerAdapter which shows the details of current Fragment in the next Fragment. Here is the Page Adapter
public class CustomerPagerAdapter extends FragmentStatePagerAdapter
{
public SectionsPagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position) {
Gson gson = new Gson();
Customer selectedCustomer = mCustomers.get(position);
String serializedCustomer = gson.toJson(selectedCustomer);
return CustomerDetailsFragment.newInstance(serializedCustomer);
}
#Override
public int getCount()
{
return mCustomers.size();
}
#Override
public CharSequence getPageTitle(int position)
{
Customer selectedCustomer = mCustomers.get(position);
String CustomerTitle = selectedCustomer.getFirstName() + " " + selectedCustomer.getLastName();
return CustomerTitle;
}
}
And here is the Fragment where the detail are displayed
public static class CustomerDetailsFragment extends Fragment {
private Customer passedInCustomer;
public CustomerDetailsFragment() {
// Required empty public constructor
}
public static CustomerDetailsFragment newInstance(String serializedCustomer){
CustomerDetailsFragment fragment = new CustomerDetailsFragment();
if (serializedCustomer != null && !serializedCustomer.isEmpty()){
Bundle args = new Bundle();
args.putString("customerInfo", serializedCustomer);
fragment.setArguments(args);
}
return fragment;
}
private void getPosition(){
Bundle args = getArguments();
if (args != null && args.containsKey("customerInfo")){
String serializedCustomer = (getArguments().getString("customerInfo"));
Gson gson = new Gson();
passedInCustomer = gson.fromJson(serializedCustomer, Customer.class);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mRootView = inflater.inflate(R.layout.fragment_Customer_details, container, false);
if (passedInCustomer != null) {
showCustomerInfo();
}
return mRootView;
}
}
The problem stems from the fact that getItem is called twice for each swipe and the information that need to be displayed are contained in an List so when getItem is called twice the second object in the list is fetched and displayed in the screen where the first object was supposed to be displayed.
Has anyone dealt with displaying an nth number of items in a scrollable ViewPager where you have to create the Fragments on demand. If yes, can you give me suggestions how to deal with this.
Thanks
I notice that this problem is showing only when I have same fragments with diferent values ( what is probably in the most case ). So after long searching for right answer, i decided to in getItem() method every odd time return one fragment and every even time return another fragment ( These fragment are completely same, except their names:
#Override
public Fragment getItem(int position) {
if(position % 2 == 0){
return FragmentGalleryImage.newInstance(images.get(position), activity);
}else{
return FragmentGalleryImage2.newInstance(images.get(position), activity);
}
}
And i solved problem nice and smooth.
I solved the problem by calling on pager.getCurrentItem() to get the position of the currently displayed item.
I have created a viewpager layout, a class that extends FragmentActivity and a fragment. What I want is that each fragment get's passed in what position it is within the viewpager. So first viewpager is created getting the argument 0, second getting 1 etc. Then if I scroll one way or another these numbers remain a true count.
The problem is the first time a fragment is created, it seems to be created twice so the position passed is 0 then 1. However I can't scroll back but I know for sure the class is being called twice. Now as I scroll forward the position increases incrementally by one. However if I scroll back it drops immediately to three on just one page back, then continues to drop past the 1 to 0 so now I can finally see my layout for 0.
I have this:
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
PracticeFragment fragment = new PracticeFragment();
getAll.putInt("position", position);
fragment.setArguments(getAll);
return fragment;
}
#Override
public int getCount() {
return numberofQ;
}
}
It first of all runs getItem twice before even going to my fragment class so that the position is 0 then 1. Then when it gets to my fragment class it makes a layout fine, I scroll through a few (3 or 4) new pages and it adds one to the position each time then when I scroll back it says it is zero or two then the numbers continue to be just as sporadic. Finally suddenly when I scroll back to the beginning the position is again 0 so my fragment for position 0 is suddenly displayed.
I don't understand what's happening, so I'm wondering what the mistake is?
public class PracticeFragment extends Fragment {
TextView question, explain;
private ScrollView sv;
private boolean starActionBar;
private final static int version = Consts.SDKversion;
ArrayList<RadioButton> rbArray;
ArrayList<LinearLayout> lArray;
ArrayList<ImageView> ivArray;
int iRow;
SQLite info;
private String correctAnswer;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
info = new SQLite(getActivity());
starActionBar = PreferenceManager.getDefaultSharedPreferences(
getActivity()).getBoolean("star", true);
setHasOptionsMenu(true);
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
for (RadioButton r : rbArray) {
if (r.isChecked()) {
r.performClick();
}
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_pm_fragment, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.activity_main, container, false);
lArray = new ArrayList<LinearLayout>();
rbArray = new ArrayList<RadioButton>();
ivArray = new ArrayList<ImageView>();
lArray.add((LinearLayout) rootView.findViewById(R.id.PM_LinLay0));
lArray.add((LinearLayout) rootView.findViewById(R.id.PM_LinLay1));
lArray.add((LinearLayout) rootView.findViewById(R.id.PM_LinLay2));
lArray.add((LinearLayout) rootView.findViewById(R.id.PM_LinLay3));
lArray.add((LinearLayout) rootView.findViewById(R.id.PM_LinLay4));
for (LinearLayout l : lArray) {
l.setOnTouchListener(PracticeFragment.this);
l.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (((ViewGroup) v).getChildAt(0).isEnabled()) {
((ViewGroup) v).getChildAt(0).performClick();
}
}
});
}
rbArray.add((RadioButton) rootView.findViewById(R.id.radio0));
rbArray.add((RadioButton) rootView.findViewById(R.id.radio1));
rbArray.add((RadioButton) rootView.findViewById(R.id.radio2));
rbArray.add((RadioButton) rootView.findViewById(R.id.radio3));
rbArray.add((RadioButton) rootView.findViewById(R.id.radio4));
ivArray.add((ImageView) rootView.findViewById(R.id.ivradio0));
ivArray.add((ImageView) rootView.findViewById(R.id.ivradio1));
ivArray.add((ImageView) rootView.findViewById(R.id.ivradio2));
ivArray.add((ImageView) rootView.findViewById(R.id.ivradio3));
ivArray.add((ImageView) rootView.findViewById(R.id.ivradio4));
rootView.findViewById(R.id.bNext).setVisibility(View.GONE);
rootView.findViewById(R.id.bPrevious).setVisibility(View.GONE);
sv = (ScrollView) rootView.findViewById(R.id.svMain);
info.open();
iRow = Integer.valueOf(info.getEverything(getArguments(), getArguments().getInt("position"), "next"));
Cursor c = info.getCursor(iRow);
((TextView) rootView.findViewById(R.id.tvQuestion))
.setText((getArguments().getInt("position") + 1) + ") " + c.getString(2));
explain = (TextView) rootView.findViewById(R.id.tvExplain);
explain.setText(c.getString(9));
explain.setVisibility(View.GONE);
correctAnswer = c.getString(8);
String[] aArray = { c.getString(3), c.getString(4), c.getString(5),
c.getString(6), c.getString(7) };
c.close();
info.close();
int o = 0;
int pos = 0;
for (String s : aArray) {
LinearLayout l = lArray.get(pos);
if (s.contentEquals("BLANK")) {
l.setVisibility(View.GONE);
} else {
l.setVisibility(View.VISIBLE);
rbArray.get(pos).setText(s);
rbArray.get(pos).setOnClickListener(null);
if (o % 2 == 0) {
l.setBackgroundColor(Consts.colorAlt);
}
o++;
}
pos++;
}
return rootView;
}
}
However if I comment out everything but the viewgroup and return rootview - still the same problem.
initialize the getAll every time as a new object in getItem()
make your fragment class static
and create one method in PracticeFragment
static PracticeFragment newInstance(int num) {
PracticeFragment f = new PracticeFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
and change in adapter
#Override
public Fragment getItem(int position) {
return PracticeFragment.newInstance(position);
}
Subclassing it fixed the problem!
In the CustomPagerAdapter of the ViewPager, in instantiateItem() method I'm trying to create an TextView and then for each page set a different text depending on certain condition. Text is read from a pages Cursor. Here is a code:
#Override
public Object instantiateItem(ViewGroup collection, int position) {
sc = new ScrollView(context);
sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
sc.setFillViewport(true);
tv = new TextView(context);
if(position < count) {
tv.setText(pages.getString(1));
pages.moveToPosition(position);
}else {
tv.setText("LOCKED");
}
tv.setTag(TAG_PAGE + position);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(Color.BLACK);
tv.setTextSize(30);
sc.addView(tv);
((ViewPager) collection).addView(sc);
return sc;
}
However ViewPager behaves not as expected. The first and the second page have the same text, rest of the pages has a sign "LOCKED" as expected. When I swipe into the 4th page and come back to the first page then the first page consists of the text that suppose to be in the second page. I also tried to use myViewPager.setOffscreenPageLimit(numberOfPages) however it doesn't help.
I found this answer:
"Inside of instantiateItem, the position parameter is the position that is in need of rendering. It is NOT the position of the currently focused item that the user would see. The pages to the left and right of the currently displayed view need to be pre rendered in memory so that the animations to those screens will be smooth. "
It make sense to me but how then can I correctly display the pages content and then update it if desired? Please advise if there is different way to do it with skipping instantiateItem() method that introduce the mess and confusion into the problem. Thank you.
I have solved this problem by using a different implementation:
// Adapter class
private static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return PageFragment.newInstance(pages[index]); // Pages is an array of Strings
}
#Override
public int getCount() {
return numberOfPages;
}
}
// PageFragment class
public class PageFragment extends Fragment {
TextView tv;
public static PageFragment newInstance(String page) {
PageFragment pageFragment = new PageFragment();
Bundle bundle = new Bundle();
bundle.putString("pageContent", page);
pageFragment.setArguments(bundle);
return pageFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
tv = (TextView) view.findViewById(R.id.text_view);
tv.setText(getArguments().getString("pageContent"));
return view;
}
}
You can Create ViewPager Object and then set Listener onthis object.
ViewPager myPager = (ViewPager) findViewById(R.id.yourPagerid);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
myPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
//You can change textview word according to current page
switch (position) {
case 0:
break;
case 1:
break;
case 2:
break;
}
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// Log.d("check","onPageScrolled");
}
#Override
public void onPageScrollStateChanged(int arg0) {
// Log.d("check","onPageScrollStateChanged");
}
});