FragmentPagerAdapter + BaseAdapter + UIL not Updating - android

I am trying to setup a tab title strip using swipes to switch between the fragments as demoed in the documentation here. It works, up to a point. The gridview shows all the images as required however, both fragment 1 and fragment 2 are showing the same images. It appears that fragment 2 is overwriting the images because if you click on the image in fragment 1, the fragment 1 details screen pops up (even though it shows an image from fragment 2).
Basically, I need my ImageAdapter (BaseAdapter) to show the correct images for each separate fragment. I don't see how the second fragment is interacting with the first if there are no static elements.
Edit: I tried changing to Picasso and the same error occurred so there has to be something in my code.
Edit2: I found this answer and it does let me redraw the grid when a fragment becomes visible but that causes a noticeable flicker and it is obvious the images were wrong. The problem has to lie somwhere with UIL/Picasso thinking the gridview in the separate fragments are the same object (they do have the same images but in different orders).
public void setupFragmentSwipes() {
mDemoCollectionPagerAdapter =
new DemoCollectionPagerAdapter(
getFragmentManager());
mViewPager = (ViewPager) mRootView.findViewById(R.id.pager);
mViewPager.setAdapter(mDemoCollectionPagerAdapter);
}
public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
String[] array = getResources().getStringArray(R.array.SortOptions);
public DemoCollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new FragmentGrid();
Bundle args = new Bundle();
args.putInt("mMode", mMode);
args.putInt("mSortAorD", mSortAorD);
args.putInt("mSortType", i);
fragment.setArguments(args);
return fragment
}
#Override
public int getCount() {
return array.length;
}
#Override
public CharSequence getPageTitle(int position) {
return array[position];
}
}
FragmentGrid
public class FragmentGrid extends Fragment {
public int mode;
private ArrayList<Theme> mThemes;
private GridView listView;
private static DisplayImageOptions options;
protected ImageLoader imageLoader = ImageLoader.getInstance();
protected int mSavedPosition;
private int sortType;
private int sortAorD;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// The last two arguments ensure LayoutParams are inflated
// properly.
View rootView = inflater.inflate(
R.layout.fragment_collection_object, container, false);
Bundle args = getArguments();
mode = args.getInt("mMode", BaseConstants.ViewModes.NORMAL);
sortType = args.getInt("mSortType", BaseConstants.Sort.POPULAR);
sortAorD = args.getInt("mSortAorD", BaseConstants.Sort.DESC);
listView = (GridView) rootView.findViewById(R.id.gridview2);
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.ic_stub)
.showImageForEmptyUri(R.drawable.ic_error)
.showImageOnFail(R.drawable.ic_error)
.cacheOnDisc(true)
.imageScaleType(ImageScaleType.EXACTLY)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
return rootView;
}
#Override
public void onResume() {
super.onResume();
listView.setSelection(mSavedPosition);
ThemeManager tm = new ThemeManager(getActivity().getApplicationContext());
mThemes = tm.getModifiedThemeList(mode);
mThemes = tm.compare(sortType, sortAorD, checkIfTesting());
listView.setAdapter(new ImageAdapter(mThemes));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSavedPosition = position;
Intent intent = new Intent(getActivity(), ImagePagerActivity.class);
intent.putExtra("mMode", mode);
intent.putExtra("mSortAorD", sortAorD);
intent.putExtra("mSortType", sortType);
intent.putExtra("mPosition", position);
startActivity(intent);
}
});
}
public class ImageAdapter extends BaseAdapter {
ArrayList<Theme> imageAdapterThemeList;
public ImageAdapter(ArrayList<Theme> themes) {
imageAdapterThemeList = themes;
}
#Override
public int getCount() {
int result = 0;
if (imageAdapterThemeList != null) {
result = imageAdapterThemeList.size();
}
return result;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = (ImageView) getActivity().getLayoutInflater().inflate(R.layout.item_grid_image, parent, false);
} else {
imageView = (ImageView) convertView;
}
Theme theme = imageAdapterThemeList.get(position);
imageLoader.displayImage(theme.getImageURL(), imageView, options);
return imageView;
}
}
fragment_collection_object.xml
<GridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:horizontalSpacing="4dip"
android:numColumns="auto_fit"
android:columnWidth="150dip"
android:scrollbars="vertical"
android:stretchMode="columnWidth"
android:verticalSpacing="4dip"
android:padding="4dip" />

The only difference between your two fragment instances is that you have mSortType in your arguments Bundle set to 0 or 1 based on the page. You only use that to set up mThemes, which you never seem to use in your ImageAdapter.
So, if you are expecting your two ImageAdapter instances to return separate results, you need to either have it pay attention to mSortType or otherwise have it vary based on the page.

I encountered a problem that sounds very similar to this a while ago. It turned out to be the animations used when swapping pages and nothing to do with the pager or adapter at all. Essentially the animation resulted in fragment1 being on top of the fragment2 but completely transparent, so the user thought they were touching fragment2, but the events were received by fragment1..which makes it behave very much like the adapter was returning the wrong fragment...
In short, if you are using animations (particularly z-order altering animations) try disabling them and see if the problem goes away. If it does, you have a problem in your animations.
Hope that helps,
Good Luck.

Ok, I found the answer but I don't know why. I had to change the following from:
ArrayList<Theme> imageAdapterThemeList;
public ImageAdapter(ArrayList<Theme> themes) {
imageAdapterThemeList = themes;
}
to:
ArrayList<Theme> imageAdapterThemeList = new ArrayList<Theme>();
public ImageAdapter(ArrayList<Theme> themes) {
for (int i = 0; i< themes.size() ;i++) {
imageAdapterThemeList.add(i, themes.get(i));
}
}
I think, rather than creating a new ArrayList I was merely pointing to the old one. This new method actually recreates it but I'm sure it's not very efficient.

Related

notifyDataSetChanged Method Takes Away Smooth Scrolling From Tabs

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

Viewpager gets the wrong page

I have looked in several topics like this but didn't see how to fix my problem
I have a resturant for say.. with dynamic number of categories.. I put all the categories in a list.. and create fragments by from those categories
so I cant just
case 0 : fragment0
case 1 :frament 1
because I dont know how much categories I have untill runtime
class MyPageAdapter extends FragmentStatePagerAdapter {
private List<MyFragment> fragments = new ArrayList<>();
private List<menuCat> Categories = new ArrayList<>();
public MyPageAdapter(FragmentManager fm, List<menuCat> Categories) {
super(fm);
this.Categories = Categories;
for (int i = 0; i<Categories.size();i++)
{
fragments.add(MyFragment.newInstance(Categories.get(i)));
}
}
#Override
public String getPageTitle(int position)
{
return Categories.get(position).catName();
}
#Override
public MyFragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
MyFragment.class
public class MyFragment extends Fragment {
public static final MyFragment newInstance(menuCat category)
{
Bundle bun = new Bundle();
bun.putString("category", category.toJson());
MyFragment f = new MyFragment();
f.setArguments(bun);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_my, container, false);
String json = getArguments().getString("category");
menuCat category = menuCat.fromJson(json);
System.out.println(category.catName());
ArrayList<Card> cards = new ArrayList<Card>();
for(menuItem item : category.getItems())
{
Card card = new Card(getActivity());
// Create a CardHeader
CardHeader header = new CardHeader(getActivity());
// Add Header to card
header.setTitle(item.getName());
card.setTitle(item.getPrice());
card.addCardHeader(header);
CardThumbnail thumb = new CardThumbnail(getActivity());
//thumb.setDrawableResource(listImages[i]);
//card.addCardThumbnail(thumb);
cards.add(card);
}
CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(), cards);
CardListView listView = (CardListView) getActivity().findViewById(R.id.myList);
if (listView != null) {
listView.setAdapter(mCardArrayAdapter);
}
return v;
}
}
this is my adapter, my problem is for example
if I am in page 1 and I need page 3 data.. if I will go to page 2.. it will display data of page 3 OR if I go to page 5 then go backward.. to 4 it will also show data of page 3.. I mean it gets me like the data of the next page instead of current one.
Creating all of your fragments in the constructor is very poor design, as you're creating references to these objects which will later be attached to an Activity, but when they are detached, you continue to hold the reference. In the end, this is going to cause you a lot of frustration with memory leaks.
Is it not possible to simply remove fragments and change your methods to the following:
#Override
public MyFragment getItem(int position) {
return MyFragment.newInstance(Categories.get(position));
}
#Override
public int getCount() {
return Categories.size();
}
I'm not certain this will solve all of your problems, but it is a start.

Nested fragments: java.lang.IllegalArgumentException: No view found for id

I have a fragment A, containing a listview. To this listview I add a listheader containing a ViewPager that pages through childfragments.
When the user clicks an item in the list, the listfragment A gets replaced by a detail-view-fragment of that listitem.
I want the user to be able to go back to the list by clicking the back button.
So far everything works, except when the user presses the back button to pop the detail fragment from the stack to get back to the listview fragment A, the app crashes with an
java.lang.IllegalArgumentException: No view found for id 0x7f06002e (com.makamedia.hockeyweb:id/news_header_pager) for fragment NewsHeaderFragment{41f7b6f8 #0 id=0x7f06002e android:switcher:2131099694:0}
My suspicion is, that maybe the nested fragments for the viewpager in the listheader get recreated before the viewpager gets recreated, thus crashing the app, but I am not sure.
Any help is appreciated!
My ViewPagerAdapter for the listheader-viewpager (removed some unrelated code):
public class NewsHeaderAdapter extends FragmentPagerAdapter {
private int mCount;
public final NewsListAdapter mListAdapter;
public NewsHeaderAdapter(FragmentManager fm, int count, long autoSwipeInterval, NewsListAdapter adapter) {
super(fm);
this.mCount = count;
this.mListAdapter = adapter;
}
#Override
public Fragment getItem(int pos) {
return NewsHeaderFragment.getNew(this.mListAdapter.getItem(pos));
}
public void setCount(int newCount){
if(newCount < 1){
this.mCount = 1;
} else if(newCount >= this.mListAdapter.getCount()){
this.mCount = this.mListAdapter.getCount();
} else {
this.mCount = newCount;
}
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return this.mListAdapter.getItem(position).getTitle();
}
}
My news detail fragment (pretty straight forward):
public class NewsHeaderFragment extends Fragment {
private NewsItem mNewsItem;
private TextView mHeaderNewsBigTitle;
private ImageView mHeaderNewsBigImage;
// Convenience method for creating a new fragment with parameters
public static NewsHeaderFragment getNew(NewsItem item){
NewsHeaderFragment fragment = new NewsHeaderFragment();
Bundle args = new Bundle();
args.putSerializable(Constants.SIG_NEWS_ITEM, item);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.row_big_news, container, false);
Bundle newsHeaderArgs = getArguments();
mNewsItem = (NewsItem)newsHeaderArgs.getSerializable(Constants.SIG_NEWS_ITEM);
setupUI(rootView);
fillUI();
return rootView;
}
private void fillUI() {
mHeaderNewsBigTitle.setText(mNewsItem.getTitle());
Picasso.with(getActivity()).load(mNewsItem.getImageBig2x()).into(mHeaderNewsBigImage);
}
private void setupUI(View rootView) {
mHeaderNewsBigTitle = (TextView) rootView.findViewById(R.id.news_big_title);
mHeaderNewsBigImage = (ImageView) rootView.findViewById(R.id.news_big_img);
}
}
My viewpager is declared in xml in a row-layout and added like so:
private void addHeaderPager(int count) {
if(mNewsListAdapter != null && mNewsListAdapter.getCount()>0) {
if (count >= mNewsListAdapter.getCount()) {
count = mNewsListAdapter.getCount() - 1;
}
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mHeader = (RelativeLayout) inflater.inflate(R.layout.row_big_news_pager, null);
mHeaderPager = (ViewPager) mHeader.findViewById(R.id.news_header_pager);
mHeaderPagerAdapter = new NewsHeaderAdapter(getChildFragmentManager(), count, 6000, mNewsListAdapter);
mHeaderPager.setOffscreenPageLimit(count);
mHeaderPager.setAdapter(mHeaderPagerAdapter);
// Bind the title indicator to the adapter
CirclePageIndicator circleIndicator = (CirclePageIndicator) mHeader.findViewById(R.id.news_header_pager_indicator);
circleIndicator.setViewPager(mHeaderPager);
mNewsListView.addHeaderView(mHeader);
}
}
Are you sure tha you use the right FragmentManager in addHeaderPager()?
I normally use getFragmentManager() and if there is a parent fragment I have to use getParentFragment().getFragmentManager() - if I don't I get the same error ("No view found for id") when trying to replace the current visible fragment.

Setting Data in ViewPagerIndicatorin different layouts

I have 3 different layouts on my ViewPagerIndicator - https://github.com/JakeWharton/Android-ViewPagerIndicator
My project is to load data from the website and set the data accordingly into the 3 different layout of the ViewPager.
How do I go about achieving it?
In my TestFragment, I have
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.i("mContent", mContent);
View view = null;
if (mContent.equalsIgnoreCase("title1")) {
view = inflater.inflate(R.layout.one, container, false);
} else if (mContent.equalsIgnoreCase("title2")) {
view = inflater.inflate(R.layout.two, container, false);
} else if (mContent.equalsIgnoreCase("title3")) {
view = inflater.inflate(R.layout.three, container, false);
//Textview data = (TextView)view.findViewById(id)
}
return view;
}
I know this is for setting layout according to the fling'd titles.
I know how to findViewById to get the id of the widgets and set data. However, the data is set statically in the code itself.
For me, I have to grab data from the internet and put the data in accordingly. How do I achieved that?
Thanks in advance.
I almost know what you want.Here is my answers,and it works well.
You can according to the following steps.
1.Create three different Fragment class ,such as LoadImageFragment1,LoadImageFragment2,LoadImageFragment3, and all of them extends Fragment.
Follow is my example.
public class LoadImageFragment1 extends Fragment {
ListView listView;
LayoutInflater mInflater;
LoadImageAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mInflater = inflater;
View view = inflater.inflate(R.layout.loadimage_layout, null);
listView = (ListView) view.findViewById(R.id.listview);
adapter = new LoadImageAdapter();
adapter.initList();
listView.setAdapter(adapter);
return view;
}
private class LoadImageAdapter extends BaseAdapter{
ArrayList<String> list = new ArrayList<String>();
public void initList(){
for(int i = 0 ; i < 30;i ++){
list.add("" + i);
}
}
#Override
public int getCount() {
return list.size();
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = mInflater.inflate(R.layout.loadimage_item, null);
}
return convertView;
}
}
2.Then modify your SampleTabsDefault class.
class GoogleMusicAdapter extends FragmentPagerAdapter {
public GoogleMusicAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// return TestFragment.newInstance(CONTENT[position % CONTENT.length]);
switch(position){
case 0:
return new LoadImageFragment1();
case 1:
return new LoadImageFragment2();
case 2:
return new LoadImageFragment3();
}
}
#Override
public CharSequence getPageTitle(int position) {
return CONTENT[position % CONTENT.length].toUpperCase();
}
#Override
public int getCount() {
return CONTENT.length;
}
}
And I just use the class of SampleTabsDefault as a sample.You can do this in other classes.
Here I just use a ListView as a sample , you can load your images and texts from internt, and you also can do other things in your LoadImageAdapter 's getView() method.
Hope this can help you.
I have 3 different layouts on my ViewPagerIndicator
ViewPagerIndicator is just an indicator
I thought your meaning is your viewadapter have three views to show.
Use FragmentPagerAdapter to handle different view.
you should read the ViewPagerIndicator samples
Before starting, copy the samples from ViewPagerIndicator
https://github.com/JakeWharton/Android-ViewPagerIndicator/tree/master/sample/src/com/viewpagerindicator/sample
The steps to be done are,
Create an custom FragmentPagerAdapter (ex: https://github.com/JakeWharton/Android-ViewPagerIndicator/blob/master/sample/src/com/viewpagerindicator/sample/TestFragmentAdapter.java)
Create custom Fragment for each each layout (in your case 3)
https://github.com/JakeWharton/Android-ViewPagerIndicator/blob/master/sample/src/com/viewpagerindicator/sample/TestFragment.java
Override onActivityCreated in the fragment class
In the onActivityCreated method, u can access the UI components as in onCreate method
So that u can make a web service call, bind it to ur UI components in this segment.
Cheers
Create and implement a Loader (AsyncTaskLoader) to load data from the internet.
Have your TestFragment implement LoaderCallbacks, so that it gets notified when the Loader (AsyncTaskLoader) has finished loading data from the internet.
In your implementation of your TestFragment's onActivityCreated, initialize or restart the loader:
getLoaderManager().initLoader(0, null, this);
In your TestFragment's implementation of onLoadFinished, assign the data to the widgets.
https://developer.android.com/reference/android/content/AsyncTaskLoader.html
https://developer.android.com/reference/android/app/LoaderManager.html
https://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html

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