notifyDataSetChanged Method Takes Away Smooth Scrolling From Tabs - android

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;
}

Related

How to properly use ViewPager in android

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;
}

Change Fragment with ViewPager

I am using PagerSlidingTab Library for ViewPager. And I want to change Fragment while scrolling of tabs. It is working fine. Check out my code.
I am using AsynTask() on each Fragment.
When the App opens with the MainActivity, First Fragment is attached to the activity, But It shows two AsynTask() dialog message, one from First and another from Second Fragment. And When I scroll to second tab, It shows dialog message of Third Fragment.
So, If I scroll from left to right in tabs, the Fragment right to the current fragment is displayed and if i scroll from right to left, the Fragment left to the current Fragment is displayed.
Please help me to solve the problem.
My Code:
public class PageSlidingTabStripFragment extends Fragment {
public static final String TAG = PageSlidingTabStripFragment.class
.getSimpleName();
public static PageSlidingTabStripFragment newInstance() {
return new PageSlidingTabStripFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.pager, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) view
.findViewById(R.id.tabs);
ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
MyPagerAdapter adapter = new MyPagerAdapter(getChildFragmentManager());
pager.setAdapter(adapter);
tabs.setViewPager(pager);
}
public class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
private final String[] TITLES = { "Instant Opportunity", "Events",
"Experts" };
#Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
#Override
public int getCount() {
return TITLES.length;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new InstantOpportunity();
case 1:
return new Events();
case 2:
return new Experts();
default:
break;
}
return null;
}
}
}
Explanation:
It turns out there is an easier implementation for scrollable tabs which doesn't involve another library. You can easily implement tabs into your app using normal Android code straight from the default SDK.
The Code
Main Class:
public class PageSlidingTabStripFragment extends Fragment {
//Variables
private ViewPager viewPager;
private PagerTitleStrip pagerTitleStrip;
public PageSlidingTabStripFragment() {
// Required empty public constructor
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Find your pager declared in XML
viewPager = (ViewPager) getView().findViewById(R.id.pager);
//Set the viewPager to a new adapter (see below)
viewPager.setAdapter(new MyAdapter(getFragmentManager()));
//If your doing scrollable tabs as opposed to fix tabs,
//you need to find a pagerTitleStrip that is declared in XML
//just like the pager
pagerTitleStrip = (PagerTitleStrip)
getView().findViewById(R.id.pager_title_strip);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.[your layout name here], container, false);
}
}
Adapter:
//Note: this can go below all of the previous code. Just make sure it's
//below the last curly bracket in your file!
class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int arg0) {
Fragment fragment = null;
if (arg0 == 0) {
fragment = new InstantOpportunity();
}
if (arg0 == 1) {
fragment = new Events();
}
if (arg0 == 2) {
fragment = new Experts();
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) {
return "Instant Opportunity";
}
if (position == 1) {
return "Events";
}
if (position == 2) {
return "Experts";
}
return null;
}
}
Conclusion:
I hope this helps you understand another way to make scrollable tabs! I have examples on my Github Page about how to make each type (That being Fixed or Scrollable).
Links:
Fixed Tabs Example - Click Here
Scrollable Tabs Example - Click Here
Hope this helps!
Edit:
When asked what to import, make sure you select the V4 support fragments.
please use this example..its very easy.i already implement that.
reference link
hope its useful to you.its best example of pager-sliding-tabstrip.
Use
framelayout compulsory:
FrameLayout fl = new FrameLayout(getActivity());
fl.addView(urFragementView);
and then set your fragement view in this framelayout.

Fragment in ViewPager not restored after popBackStack

Problem
A Fragment is not reattached to its hosting ViewPager after returning from another fragment.
Situation
One Activity hosting a Fragment whose layout holds a ViewPager (PageListFragment in the example below). The ViewPager is populated by a FragmentStateViewPagerAdapter. The single Fragments hosted inside the pager (PageFragment in the example below) can open sub page lists, containing a new set of pages.
Behaviour
All works fine as long as the back button is not pressed. As soon as the user closes one of the sub PageLists the previous List is recreated, but without the Page that was displayed previously. Swiping through the other pages on the parent PageList still works.
Code
A sample application can be found on github:
Activity
public class MainActivity extends FragmentActivity {
private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";
public static final String ARG_PARENTS = "Parents";
public void goInto(String mHostingLevel, String mPosition) {
Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
addFragment(hostingFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addBaseFragment();
}
private void addBaseFragment() {
Fragment hostingFragment = newHostingFragment("", "");
addFragment(hostingFragment);
}
private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
Fragment hostingFragment = new PageListFragment();
Bundle args = new Bundle();
args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
hostingFragment.setArguments(args);
return hostingFragment;
}
private void addFragment(Fragment hostingFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
}
PageListFragment
public class PageListFragment extends Fragment {
private String mParentString;
public PageListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hosting, container, false);
}
#Override
public void onResume() {
mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
super.onResume();
}
private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private String mHostingLevel;
public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
super(fm);
this.mHostingLevel = hostingLevel;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
PageFragment pageFragment = new PageFragment();
Bundle args = new Bundle();
args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
args.putInt(PageFragment.ARG_POSITION, position);
pageFragment.setArguments(args);
return pageFragment;
}
#Override
public int getCount() {
return 5;
}
}
}
PageFragment
public class PageFragment extends Fragment {
public static final String ARG_POSITION = "Position";
private String mHostingLevel;
private int mPosition;
public PageFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_page, container, false);
setupTextView(contentView);
setupButton(contentView);
return contentView;
}
private void setupTextView(View contentView) {
mPosition = getArguments().getInt(ARG_POSITION);
mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
TextView text = (TextView) contentView.findViewById(R.id.textView);
text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}
private void setupButton(View contentView) {
Button button = (Button) contentView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openNewLevel();
}
});
}
protected void openNewLevel() {
MainActivity activity = (MainActivity) getActivity();
activity.goInto(mHostingLevel, Integer.toString(mPosition));
}
}
After a lengthy investigation it turns out to be a problem with the fragment manager.
When using a construct like the one above the fragment transaction to reattach the fragment to the page list is silently discarded. It is basically the same problem that causes a
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
when trying to alter the fragments inside the FragmentPager.
The same solution, as for problems with this error, is also applicable here. When constructing the FragmentStatePagerAdapter supply the correct child fragment manager.
Instead of
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
do
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
See also: github
What Paul has failed to mention is, if you use getChildFragmentManager, then you will suffer the "blank screen on back pressed" issue.
The hierarchy in my case was:
MainActivity->MainFragment->TabLayout+ViewPager->AccountsFragment+SavingsFragment+InvestmentsFragment etc.
The problem I had was that I couldn't use childFragmentManagerfor the reason that a click on the item Account view (who resides inside one of the Fragments of the ViewPager) needed to replace MainFragment i.e. the entire screen.
Using MainFragments host Fragment i.e. passing getFragmentManager() enabled the replacing, BUT when popping the back-stack, I ended up with this screen:
This was apparent also by looking at the layout inspector where the ViewPager is empty.
Apparently looking at the restored Fragments you would notice that their View is restored but will not match the hierarchy of the popped state. In order to make the minimum impact and not force a re-creation of the Fragments I re-wrote FragmentStatePagerAdapter with the following changes:
I copied the entire code of FragmentStatePagerAdapter and changed
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
}
with
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(f);
mCurTransaction.attach(f);
return f;
}
}
...
}
This way I am effectively making sure that that the restored Fragments are re-attached to the ViewPager.
Delete all page fragments, enabling them to be re-added later
The page fragments are not attached when you return to the viewpager screen as the FragmentStatePagerAdapter is not re-connecting them. As a work-around, delete all the fragments in the viewpager after popbackstack() is called, which will allow them to be re-added by your initial code.
[This example is written in Kotlin]
//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
val item = childFragmentManager.findFragmentByTag("f$i")
if (item != null) {
adapter.destroyItem(container!!, i, item)
}
}

How to display different text on each page in ViewPager, instantiateItem() confusion

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");
}
});

Why `PagerAdapter::notifyDataSetChanged` is not updating the View?

I'm using the ViewPager from the compatibility library. I have succussfully got it displaying several views which I can page through.
However, I'm having a hard time figuring out how to update the ViewPager with a new set of Views.
I've tried all sorts of things like calling mAdapter.notifyDataSetChanged(), mViewPager.invalidate() even creating a brand new adapter each time I want to use a new List of data.
Nothing has helped, the textviews remain unchanged from the original data.
Update:
I made a little test project and I've almost been able to update the views. I'll paste the class below.
What doesn't appear to update however is the 2nd view, the 'B' remains, it should display 'Y' after pressing the update button.
public class ViewPagerBugActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
Button updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
updateViewPager();
}
});
}
private void updateViewPager() {
data.clear();
data.add("X");
data.add("Y");
data.add("Z");
myViewPager.getAdapter().notifyDataSetChanged();
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
#Override
public void startUpdate(View arg0) {
}
#Override
public void finishUpdate(View arg0) {
}
}
}
There are several ways to achieve this.
The first option is easier, but bit more inefficient.
Override getItemPosition in your PagerAdapter like this:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
This way, when you call notifyDataSetChanged(), the view pager will remove all views and reload them all. As so the reload effect is obtained.
The second option, suggested by Alvaro Luis Bustamante (previously alvarolb), is to setTag() method in instantiateItem() when instantiating a new view. Then instead of using notifyDataSetChanged(), you can use findViewWithTag() to find the view you want to update.
Conclusion
If you have a lot of views, or want to support modifying any specific item and/or view (fastly at any time), then the second approach (tagging) is very flexible and high performant, as it prevents recreating all the not modified views.
(Kudos to alvarolb for the original research.)
But if your App has only a "refresh" feature (without single item changes being even allowed), or has just few items, use the first approach, as it saves development time.
I don't think there is any kind of bug in the PagerAdapter. The problem is that understanding how it works is a little complex. Looking at the solutions explained here, there is a misunderstanding and therefore a poor usage of instantiated views from my point of view.
The last few days I have been working with PagerAdapter and ViewPager, and I found the following:
The notifyDataSetChanged() method on the PagerAdapter will only notify the ViewPager that the underlying pages have changed. For example, if you have created/deleted pages dynamically (adding or removing items from your list) the ViewPager should take care of that. In this case I think that the ViewPager determines if a new view should be deleted or instantiated using the getItemPosition() and getCount() methods.
I think that ViewPager, after a notifyDataSetChanged() call takes it's child views and checks their position with the getItemPosition(). If for a child view this method returns POSITION_NONE, the ViewPager understands that the view has been deleted, calling the destroyItem(), and removing this view.
In this way, overriding getItemPosition() to always return POSITION_NONE is completely wrong if you only want to update the content of the pages, because the previously created views will be destroyed and new ones will be created every time you call notifyDatasetChanged(). It may seem to be not so wrong just for a few TextViews, but when you have complex views, like ListViews populated from a database, this can be a real problem and a waste of resources.
So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.
Imagine for example that you have 100 pages with 100 TextViews and you only want to update one value periodically. With the approaches explained before, this means you are removing and instantiating 100 TextViews on each update. It does not make sense...
Change the FragmentPagerAdapter to FragmentStatePagerAdapter.
Override getItemPosition() method and return POSITION_NONE.
Eventually, it will listen to the notifyDataSetChanged() on view pager.
The answer given by alvarolb is definitely the best way to do it. Building upon his answer, an easy way to implement this is to simply store out the active views by position:
SparseArray<View> views = new SparseArray<View>();
#Override
public Object instantiateItem(View container, int position) {
View root = <build your view here>;
((ViewPager) container).addView(root);
views.put(position, root);
return root;
}
#Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
views.remove(position);
view = null;
}
Then once by overriding the notifyDataSetChanged method you can refresh the views...
#Override
public void notifyDataSetChanged() {
int key = 0;
for(int i = 0; i < views.size(); i++) {
key = views.keyAt(i);
View view = views.get(key);
<refresh view with new data>
}
super.notifyDataSetChanged();
}
You can actually use similar code in instantiateItem and notifyDataSetChanged to refresh your view. In my code I use the exact same method.
Had the same problem. For me it worked to extend FragmentStatePagerAdapter, and override the below methods:
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
After hours of frustration while trying all the above solutions to overcome this problem and also trying many solutions on other similar questions like this, this and this which all FAILED with me to solve this problem and to make the ViewPager to destroy the old Fragment and fill the pager with the new Fragments. I have solved the problem as following:
1) Make the ViewPager class to extends FragmentPagerAdapter as following:
public class myPagerAdapter extends FragmentPagerAdapter {
2) Create an Item for the ViewPager that store the title and the fragment as following:
public class PagerItem {
private String mTitle;
private Fragment mFragment;
public PagerItem(String mTitle, Fragment mFragment) {
this.mTitle = mTitle;
this.mFragment = mFragment;
}
public String getTitle() {
return mTitle;
}
public Fragment getFragment() {
return mFragment;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public void setFragment(Fragment mFragment) {
this.mFragment = mFragment;
}
}
3) Make the constructor of the ViewPager take my FragmentManager instance to store it in my class as following:
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
4) Create a method to re-set the adapter data with the new data by deleting all the previous fragment from the fragmentManager itself directly to make the adapter to set the new fragment from the new list again as following:
public void setPagerItems(ArrayList<PagerItem> pagerItems) {
if (mPagerItems != null)
for (int i = 0; i < mPagerItems.size(); i++) {
mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
}
mPagerItems = pagerItems;
}
5) From the container Activity or Fragment do not re-initialize the adapter with the new data. Set the new data through the method setPagerItems with the new data as following:
ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));
mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();
I hope it helps.
I had the same issue and my solution is using FragmentPagerAdapter with overriding of FragmentPagerAdapter#getItemId(int position):
#Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
By default, this method returns item's position. I suppose that ViewPager checks if itemId was changed and recreates page only if it was. But not-overriden version returns the same position as itemId even if page is actually different, and ViewPager doesn't define that page is replaced one and needs to be recreated.
To use this, long id is needed for each page. Normally it is expected to be unique, but i suggest, for this case, that it just should be different from the previous value for the same page. So, It is possible to use continuous counter in adapter or random integers (with wide distribution) here.
I think that it is more consistent way rather using of Tags of view mentioned as a solution in this topic. But probably not for all cases.
I found very interesting decision of this problem.
Instead of using FragmentPagerAdapter, which keep in memory all fragments, we can use FragmentStatePagerAdapter (android.support.v4.app.FragmentStatePagerAdapter), that reload fragment each time, when we select it.
Realisations of both adapters are identical. So, we need just change "extend FragmentPagerAdapter" on "extend FragmentStatePagerAdapter"
ViewPager was not designed to support dynamic view change.
I had confirmation of this while looking for another bug related to this one https://issuetracker.google.com/issues/36956111 and in particular https://issuetracker.google.com/issues/36956111#comment56
This question is a bit old, but Google recently solved this problem with ViewPager2 .
It will allow to replace handmade (unmaintained and potentially buggy) solutions by a standard one. It also prevents recreating views needlessly as some answers do.
For ViewPager2 examples, you can check https://github.com/googlesamples/android-viewpager2
If you want to use ViewPager2, you will need to add the following dependency in your build.gradle file :
dependencies {
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
}
Then you can replace your ViewPager in your xml file with :
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
After that, you will need to replace ViewPager by ViewPager2 in your activity
ViewPager2 needs either a RecyclerView.Adapter, or a FragmentStateAdapter, in your case it can be a RecyclerView.Adapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private ArrayList<String> arrayList = new ArrayList<>();
public MyAdapter(Context context, ArrayList<String> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.tvName.setText(arrayList.get(position));
}
#Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
}
}
}
In the case you were using a TabLayout, you can use a TabLayoutMediator :
TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
#Override
public void onConfigureTab(#NotNull TabLayout.Tab tab, int position) {
// configure your tab here
tab.setText(tabs.get(position).getTitle());
}
});
tabLayoutMediator.attach();
Then you will be able to refresh your views by modifying your adapter's data and calling notifyDataSetChanged method
All these solution did not help me. thus i found a working solution:
You can setAdapter every time, but it isn't enough.
you should do these before changing adapter:
FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
transaction.remove(f);
}
transaction.commit();
and after this:
viewPager.setAdapter(adapter);
After a lot of searching for this problem, I found a really good solution that I think is the right way to go about this. Essentially, instantiateItem only gets called when the view is instantiated and never again unless the view is destroyed (this is what happens when you override the getItemPosition function to return POSITION_NONE). Instead, what you want to do is save the created views and either update them in the adapter, generate a get function so someone else can update it, or a set function which updates the adapter (my favorite).
So, in your MyViewPagerAdapter add a variable like:
private View updatableView;
an in your instantiateItem:
public Object instantiateItem(View collection, int position) {
updatableView = new TextView(ctx); //My change is here
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
so, this way, you can create a function that will update your view:
public updateText(String txt)
{
((TextView)updatableView).setText(txt);
}
Hope this helps!
Two and half years after the OP posed his question, this issue is still, well, still an issue. It's obvious Google's priority on this isn't particularly high, so rather than find a fix, I found a workaround. The big breakthrough for me was finding out what the real cause of the problem was (see the accepted answer in this post ). Once it was apparent that the issue was that any active pages are not properly refreshed, my workaround was obvious:
In my Fragment (the pages):
I took all the code which populates the form out of onCreateView and put it in a function called PopulateForm which may be called from anywhere, rather than by the framework. This function attempts to get the current View using getView, and if that is null, it just returns. It's important that PopulateForm contains only the code that displays - all the other code which creates FocusChange listeners and the like is still in OnCreate
Create a boolean which can be used as a flag indicating the form must be reloaded. Mine is mbReloadForm
Override OnResume() to call PopulateForm() if mbReloadForm is set.
In my Activity, where I do the loading of the pages:
Go to page 0 before changing anything. I'm using FragmentStatePagerAdapter, so I know that two or three pages are affected at most. Changing to page 0 ensures I only ever have the problem on pages 0, 1 and 2.
Before clearing the old list, take it's size(). This way you know how many pages are affected by the bug. If > 3, reduce it to 3 - if you're using a a different PagerAdapter, you'll have to see how many pages you have to deal with (maybe all?)
Reload the data and call pageAdapter.notifyDataSetChanged()
Now, for each of the affected pages, see if the page is active by using pager.getChildAt(i) - this tells you if you have a view. If so, call pager.PopulateView(). If not, set the ReloadForm flag.
After this, when you reload a second set of pages, the bug will still cause some to display the old data. However, they will now be refreshed and you will see the new data - your users won't know the page was ever incorrect because this refreshing will happen before they see the page.
Hope this helps someone!
A much easier way: use a FragmentPagerAdapter, and wrap your paged views onto fragments. They do get updated
Thank rui.araujo and Alvaro Luis Bustamante. At first, I try to use rui.araujo's way, because it's easy. It works but when the data change, the page will redraw obviously. It is bad so I try to use Alvaro Luis Bustamante's way. It's perfect. Here is the code:
#Override
protected void onStart() {
super.onStart();
}
private class TabPagerAdapter extends PagerAdapter {
#Override
public int getCount() {
return 4;
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}
#Override
public void destroyItem(final View container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View view = LayoutInflater.from(
getBaseContext()).inflate(R.layout.activity_approval, null, false);
container.addView(view);
ListView listView = (ListView) view.findViewById(R.id.list_view);
view.setTag(position);
new ShowContentListTask(listView, position).execute();
return view;
}
}
And when data change:
for (int i = 0; i < 4; i++) {
View view = contentViewPager.findViewWithTag(i);
if (view != null) {
ListView listView = (ListView) view.findViewById(R.id.list_view);
new ShowContentListTask(listView, i).execute();
}
}
I had a similar problem in which I had four pages and one of the pages updated views on the other three. I was able to updated the widgets(SeekBars, TextViews, etc.) on the page adjacent to the current page. The last two pages would have uninitialized widgets when calling mTabsAdapter.getItem(position).
To solve my issue, I used setSelectedPage(index) before calling getItem(position). This would instantiate the page, allowing me to be able to alter values and widgets on each page.
After all of the updating I would use setSelectedPage(position) followed by notifyDataSetChanged().
You can see a slight flicker in the ListView on the main updating page, but nothing noticeable. I haven't tested it throughly, but it does solve my immediate problem.
Just in case anyone are using FragmentStatePagerAdapter based adapter(which will let ViewPager create minimum pages needed for display purpose, at most 2 for my case), #rui.araujo's answer of overwriting getItemPosition in your adapter will not cause significant waste, but it still can be improved.
In pseudo code:
public int getItemPosition(Object object) {
YourFragment f = (YourFragment) object;
YourData d = f.data;
logger.info("validate item position on page index: " + d.pageNo);
int dataObjIdx = this.dataPages.indexOf(d);
if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
logger.info("data changed, discard this fragment.");
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
I am just posting this answer in case anyone else finds it useful. For doing the exact same thing, I simply took the source code of the ViewPager and PagerAdapter from the compatibility library and compiled it within my code (You need to sort out all the errors and imports yourself, but it definitely can be done).
Then, in the CustomViewPager, create a method called updateViewAt(int position). The view itself can be gotten from ArrayList mItems defined in the ViewPager class (you need to set an Id for the views at instantiate item and compare this id with position in the updateViewAt() method). Then you can update the view as necessary.
You can update dynamically all fragments, you can see in three steps.
In your adapter:
public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mFragmentTags = new HashMap<Integer, String>();
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return FirstFragment.newInstance();
case 1:
return SecondFragment.newInstance();
case 2:
return ThirdFragment.newInstance();
default:
return null;
}
}
// Returns the page title for the top indicator
#Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
mFragmentTags.put(position, tag);
}
return object;
}
public Fragment getFragment(int position) {
Fragment fragment = null;
String tag = mFragmentTags.get(position);
if (tag != null) {
fragment = mFragmentManager.findFragmentByTag(tag);
}
return fragment;
}}
Now in your activity:
public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{
MyPagerAdapter mAdapterViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapterViewPager);
viewPager.addOnPageChangeListener(this);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
Fragment fragment = mAdapterViewPager.getFragment(position);
if (fragment != null) {
fragment.onResume();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}}
Finally in your fragment, something like that:
public class YourFragment extends Fragment {
// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {
return new YourFragment();
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment, container, false);
}
#Override
public void onResume() {
super.onResume();
//to refresh your view
refresh();
}}
You can see complete code here.
Thanks Alvaro Luis Bustamante.
I guess, I've got the logics of ViewPager.
If I need to refresh a set of pages and display them based on new dataset, I call notifyDataSetChanged().
Then, ViewPager makes a number of calls to getItemPosition(), passing there Fragment as an Object. This Fragment can be either from an old dataset (that I want to discard) or from a new one (that I want to display). So, I override getItemPosition() and there I have to determine somehow if my Fragment is from the old dataset or from the new one.
In my case I have a 2-pane layout with a list of top items on the left pane and a swipe view (ViewPager) on the right. So, I store a link to my current top item inside my PagerAdapter and also inside of each instantiated page Fragment.
When the selected top item in the list changes, I store the new top item in PagerAdapter and call notifyDataSetChanged(). And in the overridden getItemPosition() I compare the top item from my adapter to the top item from my fragment.
And only if they are not equal, I return POSITION_NONE.
Then, PagerAdapter reinstantiates all the fragments that have returned POSITION_NONE.
NOTE. Storing the top item id instead of a reference might be a better idea.
The code snippet below is a bit schematical but I adapted it from the actually working code.
public class SomeFragment extends Fragment {
private TopItem topItem;
}
public class SomePagerAdapter extends FragmentStatePagerAdapter {
private TopItem topItem;
public void changeTopItem(TopItem newTopItem) {
topItem = newTopItem;
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
}
Thanks for all the previous researchers!
The code below worked for me.
Create a class which extends the FragmentPagerAdapter class as below.
public class Adapter extends FragmentPagerAdapter {
private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;
public Adapter(FragmentManager fm) {
super(fm);
}
public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
super(fm);
mActivity = mA;
mFragmentManager = fm;
object = new ArrayList<>();
mFragmentTags = new HashMap<Integer, String>();
this.tabCount = numberOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment0.newInstance(mActivity);
case 1:
return Fragment1.newInstance(mActivity);
case 2:
return Fragment2.newInstance(mActivity);
default:
return null;
}}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Log.e("Already defined","Yes");
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
Log.e("Fragment Tag","" + position + ", " + tag);
mFragmentTags.put(position, tag);
}else{
Log.e("Already defined","No");
}
container_id = container.getId();
this.container = container;
if(position == 0){
this.object.add(0,object);
}else if(position == 1){
this.object.add(1,object);
}else if(position == 2){
this.object.add(2,object);
}
return object;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (object instanceof Fragment) {
Log.e("Removed" , String.valueOf(position));
}
}
#Override
public int getItemPosition (Object object)
{ int index = 0;
if(this.object.get(0) == object){
index = 0;
}else if(this.object.get(1) == object){
index = 1;
}else if(this.object.get(2) == object){
index = 2;
}else{
index = -1;
}
Log.e("Index" , "..................." + String.valueOf(index));
if (index == -1)
return POSITION_NONE;
else
return index;
}
public String getFragmentTag(int pos){
return "android:switcher:"+R.id.pager+":"+pos;
}
public void NotifyDataChange(){
this.notifyDataSetChanged();
}
public int getcontainerId(){
return container_id;
}
public ViewGroup getContainer(){
return this.container;
}
public List<Object> getObject(){
return this.object;
}
#Override
public int getCount() {
return tabCount;
}}
Then inside each Fragment you created, create an updateFragment method. In this method you change the things you need to change in the fragment. For example in my case, Fragment0 contained a GLSurfaceView which displays a 3d object based on a path to a .ply file, so inside my updateFragment method I change the path to this ply file.
then create a ViewPager instance,
viewPager = (ViewPager) findViewById(R.id.pager);
and an Adpater instance,
adapter = new Adapter(getSupportFragmentManager(), 3, this);
then do this,
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);
Then inside the class were you initialized the Adapter class above and created a viewPager, every time you want to update one of your fragments (in our case Fragment0) use the following:
adapter.NotifyDataChange();
adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.
fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.
fragment0.updateFragment() method which include the updates on this fragment
adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.
This solution was based on the technique suggested by Alvaro Luis Bustamante.
I know I'm late but still it could help someone. I'm just extending the accepted answer and I have also added the comment on it.
Well, the answer itself says it is inefficient
So in order to make it refresh only when required you can do this
private boolean refresh;
public void refreshAdapter() {
refresh = true;
notifyDataSetChanged();
}
#Override
public int getItemPosition(#NonNull Object object) {
if (refresh) {
refresh = false;
return POSITION_NONE;
} else {
return super.getItemPosition(object);
}
}
In ViewPager2 you can re-initialize the adapter again to refresh the pager list with new views. viewPager2.adapter = myPagerAdapter
1.First you have to set the getItemposition method in your Pageradapter class
2.You have to read the Exact position of your View Pager
3.then send that position as data location of your new one
4.Write update button onclick listener inside the setonPageChange listener
that program code is little bit i modified to set the particular position element only
public class MyActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
data.add("D");
data.add("E");
data.add("F");
myViewPager = (ViewPager) findViewById(R.id.pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
updateButton = (Button) findViewById(R.id.update);
myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int i, float v, int i2) {
//Toast.makeText(MyActivity.this, i+" Is Selected "+data.size(), Toast.LENGTH_SHORT).show();
}
#Override
public void onPageSelected( int i) {
// here you will get the position of selected page
final int k = i;
updateViewPager(k);
}
#Override
public void onPageScrollStateChanged(int i) {
}
});
}
private void updateViewPager(final int i) {
updateButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(MyActivity.this, i+" Is Selected "+data.size(), Toast.LENGTH_SHORT).show();
data.set(i, "Replaced "+i);
myViewPager.getAdapter().notifyDataSetChanged();
}
});
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
#Override
public int getCount() {
return data.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
#Override
public void startUpdate(View arg0) {
}
#Override
public void finishUpdate(View arg0) {
}
}
}
what worked for me was going viewPager.getAdapter().notifyDataSetChanged();
and in the adapter putting your code for updating the view inside getItemPosition like so
#Override
public int getItemPosition(Object object) {
if (object instanceof YourViewInViewPagerClass) {
YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
view.setData(data);
}
return super.getItemPosition(object);
}
might not be the most correct way of going about it but it worked (the return POSITION_NONE trick caused a crash for me so wasnt an option)
Always returning POSITION_NONE is simple but a little inefficient way because that evoke instantiation of all page that have already instantiated.
I've created a library ArrayPagerAdapter to change items in PagerAdapters dynamically.
Internally, this library's adapters return POSITION_NONE on getItemPosiition() only when necessary.
You can change items dynamically like following by using this library.
#Override
protected void onCreate(Bundle savedInstanceState) {
/** ... **/
adapter = new MyStatePagerAdapter(getSupportFragmentManager()
, new String[]{"1", "2", "3"});
((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
adapter.add("4");
adapter.remove(0);
}
class MyPagerAdapter extends ArrayViewPagerAdapter<String> {
public MyPagerAdapter(String[] data) {
super(data);
}
#Override
public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
View v = inflater.inflate(R.layout.item_page, container, false);
((TextView) v.findViewById(R.id.item_txt)).setText(item);
return v;
}
}
Thils library also support pages created by Fragments.
This is a horrible problem and I'm happy to present an excellent solution; simple, efficient, and effective !
See below, the code shows using a flag to indicate when to return POSITION_NONE
public class ViewPagerAdapter extends PagerAdapter
{
// Members
private boolean mForceReinstantiateItem = false;
// This is used to overcome terrible bug that Google isn't fixing
// We know that getItemPosition() is called right after notifyDataSetChanged()
// Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
#Override
public int getItemPosition(Object object)
{
if (mForceReinstantiateItem)
{
mForceReinstantiateItem = false;
return POSITION_NONE;
}
else
{
return super.getItemPosition(object);
}
}
public void setData(ArrayList<DisplayContent> newContent)
{
mDisplayContent = newContent;
mForceReinstantiateItem = true;
notifyDataSetChanged();
}
}
This is for all those like me, which need to update the Viewpager from a service (or other background thread) and none of the proposals have worked:
After a bit of logchecking i realized, that the notifyDataSetChanged() method never returns. getItemPosition(Object object) is called an all ends there without further processing. Then i found in the docs of the parent PagerAdapter class (is not in the docs of the subclasses), "Data set changes must occur on the main thread and must end with a call to notifyDataSetChanged() ".
So, the working solution in this case was (using FragmentStatePagerAdapter
and getItemPosition(Object object) set to return POSITION_NONE) :
and then the call to notifyDataSetChanged() :
runOnUiThread(new Runnable() {
#Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});
In my case there is a textView in my Viewpager, on a button click in mainActivity I want to change the color of that textView and update pagerAdapter. On the button Click I saved the color in SharedPreference and update pagerAdapter, that it can update the color taken from shared prefrence. So, I update viewPager view the following way .
btn_purple.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int color = ContextCompat.getColor(mContext, R.color.colorPrimaryDark2);
editor.putInt("sahittoFontColor", color);
editor.apply();
toNotifyDatasetChanged();
}
});
now the update method :
private void toNotifyDatasetChanged (){
if(viewPager!=null&& pagerAdapter!=null) {
viewPager.setAdapter(null);
viewPager.setAdapter(pagerAdapter);
}
}
And my pagerAdapter Was :
pagerAdapter = new Sahitto_ViewPagerAdapter (mContext, filenameParameter, 30, lineList);
viewPager.setAdapter(pagerAdapter);
And in instantiateItem was (in PagerAdapter) :
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
int bnfntcolor=settings.getInt("sahittoFontColor", 0);
if (bnfntcolor!=0){
textView.setTextColor(bnfntcolor);
}
Thus, when I click the button, the color changes immediately in pagerAdapter's Textview.
Happy coding.
Instead of returning POSITION_NONE and creating all fragments again, you can do as I suggested here: Update ViewPager dynamically?
I think I've made a simple way to notify of data set changes:
First, change a bit the way the instantiateItem function works:
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View rootView = mInflater.inflate(...,container, false);
rootView.setTag(position);
updateView(rootView, position);
container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mViewPager.setObjectForPosition(rootView, position);
return rootView;
}
for "updateView" , fill the view with all the data you wish to fill (setText,setBitmapImage,...) .
verify that destroyView works like this:
#Override
public void destroyItem(final ViewGroup container, final int position, final Object obj) {
final View viewToRemove = (View) obj;
mViewPager.removeView(viewToRemove);
}
Now, suppose you need to change the data, do it, and then call the next function on the PagerAdapter :
public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
final NotifyLocation toPos) {
final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
if (fromPosInt <= toPosInt) {
notifyDataSetChanged();
for (int i = fromPosInt; i <= toPosInt; ++i) {
final View pageView = viewPager.findViewWithTag(i);
mPagerAdapter.updateView(pageView, i);
}
}
}
public enum NotifyLocation {
MOST_LEFT, CENTER, MOST_RIGHT
}
For example if you wish to notify all of the views that are being shown by the viewPager that something has changed, you can call:
notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);
That's it.

Categories

Resources