I have a requirement to show a ViewPager displaying initially just one View.
Once this View goes through measure & layout pass, depending on the result of these I might need to change the contents of the adapter.
While doing so, I've come across a scenario where the ViewPager will not follow through with it's movement while trying to swipe to the next item from a certain position.
I'm sure I'm doing something wrong in my adapter implementation, yet I can't point my finger at the problem.
For the sake of demonstration I've created a dummy adapter that will mimic what happens on my actual project.
In order to reproduce the issue just try to swipe all the way to position 2 (3rd item), result is you can't get past position 1 (2nd item).
Thanks!
public class CustomAdapter extends PagerAdapter {
int pages = 0;
#Override
public int getCount() {
return pages;
}
#Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
public void setPages(int x) {
this.pages = x;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
TextView tv = new TextView(MainActivity.this);
ViewGroup.LayoutParams lp =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(lp);
tv.setText("Position: " + position);
if (position == 0 && pages == 1) {
setPages(2);
notifyDataSetChanged();
}
if (position == 1 && pages == 2) {
setPages(3);
notifyDataSetChanged();
}
container.addView(tv);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
Activity (onCreate or so):
CustomAdapter adapter = new CustomAdapter();
adapter.setPages(1);
vp.setAdapter(adapter);
adapter.notifyDataSetChanged();
instantiateItem item method is wrong place to do it. because it doest work for only one view it works for 3 view at same time.
try moving
if (position == 0 && pages == 1) {
setPages(2);
notifyDataSetChanged();
}
if (position == 1 && pages == 2) {
setPages(3);
notifyDataSetChanged();
}
in
setPrimaryItem Method
UPDATE:
Try to call instantiateItem method for the position you want in the setPrimaryItem method. You can do it since instantiateItem is a public method.But i have no idea how it is going to work it is a theory. Just give it a try.
Related
I have a gallery/slideshow activity that allows user to swipe between Photos. Each "Page" is just a TouchImageView. There is a pagination logic in there, and I can see that it is calling the API accordingly. However, I am not able to swipe further even after notifyDataSetChanged has been called. Here's the code:
Activity {
ViewPager vp;
CustomPagerAdapter pagerAdapter;
onCreate() {
//api callback
pagerAdapter = new CustomPagerAdapter();
vp.setAdapter(pagerAdapter);
}
}
CustomPagerAdapter() {
TouchImageView imageView;
List<Photos> photos;
int getCount() {
return photos == null ? 0 : photos.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mLayoutInflater.inflate(R.layout.pager_image, container, false);
imageView = (TouchImageView) itemView.findViewById(R.id.photo);
Glide.with(context)
.load(photos.get(position).getUrl())
.into(imageView);
container.addView(itemView);
if (position == photos.size()-1) {
loadMorePhotos();
}
return itemView;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((RelativeLayout) object);
}
void loadMorePhotos() {
//call api and stuff
void onResponse(Call call, Response<List<Photo>> response) {
photos.addAll(response.body);
notifyDataSetChanged();
}
}
}
And this is the result:
http://imgur.com/nA1TzfI
I have no idea what is going on for 2 days. Please help!
This may not be ideal, but where you call notifyDatasetChanged() you could instead try calling vp.setAdapter(pagerAdapter);
(i.e. actually re-set the adapter). A downside of this is that the pager will probably go back to having the first page selected. To avoid this, you'd need to save the latest page somewhere and restore it afterwards.
As I say, not ideal, but it may help.
You dont have to load more photos from adapter class, in your activity class you can use onPageScrolled and call the api when the last item is scrolled and set adapter again also save state of view pager before calling api to retain it when adapter is set again.
private boolean isLastPageSwiped;
private int counterPageScroll;
private OnPageChangeListener mListener = new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
if (position == 6 && positionOffset == 0 && !isLastPageSwiped){
if(counterPageScroll != 0){
isLastPageSwiped=true;
//call api
}
counterPageScroll++;
}else{
counterPageScroll=0;
}
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
};
and use this listener
vp.setOnPageChangeListener(mListener);
We can use this approach for doing pagination on ViewPager (with fragment)
load new page data but not append to the adapter
make sure the page is not scrolling, then append new page data into the adapter (ViewPager.OnPageChangeListener#onPageScrolled's positionOffsetPixels == 0).
update current item for view pager with offset = new page data's size (let's call it newPosition)
rebind data to 3 fragments: newPosition - 1, newPosition, newPosition + 1
For getting current fragment, we can use fragment manager
private fun getFragment(position: Int): PageFragment? =
supportFragmentManager.fragments.firstOrNull {
(it as? PageFragment)?.position == position // always pass possition value into the fragment
} as? PageFragment
We need to append data when not scrolling (step 2) to have a better UX to users. If we append data immediately when it comes and if user is scrolling, view pager's setCurrentItem will make a strange move.
I create a simple version here https://github.com/tuanchauict/DemoPaginationViewPager. Please take a look
I am using two Viewpager in my app,
1) First Viewpager displays images only
2) I am displaying price
now the issue is i have 4 images displaying in my viewpager1, and in second pager i have price as per selected product. first time it does not show anything, but when i scroll image and goes to next, it shows price..
pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Picasso.with(ProductLandingActivity.this) .load(categorylist.get(position).getProductLanding_packLink())
.error(R.drawable.nopreview )
.placeholder(R.drawable.progress_animation)
.into(selectedImage);
System.out.println("Selected is"+position);
selectedname.setText(categorylist.get(position).getProductLanding_packDesc());
for (int i = 0; i < categorylist.get(position).getItems().size(); i++) {
System.out.println("ProductPack_ID : " + categorylist.get(position).getItems().get(i).getPackSize_sellingPrice());
}
temp = categorylist.get(position).getItems();
packadapter = new MyPacksPagerAdapter(ProductLandingActivity.this,categorylist);
pagerpacks.setAdapter(packadapter);
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
adapter
private class MyPacksPagerAdapter extends PagerAdapter {
Context context;
ArrayList<PackListModel> packsizedata ;
public MyPacksPagerAdapter(Context context,ArrayList<PackListModel> packsizedata) {
this.context = context;
this.packsizedata = packsizedata;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
final OneActor oneActor;
View view;
LayoutInflater infl = ((Activity)context).getLayoutInflater();
view = infl.inflate(R.layout.list_item_pagerpacktitles, container,false);
oneActor = new OneActor();
// oneActor.avatar = (ImageView) view.findViewById(R.id.image);
oneActor.name = (TextView) view.findViewById(R.id.product_landing_packsname);
oneActor.cmtCount = (TextView) view.findViewById(R.id.product_landing_packsprice);
view.setTag(oneActor);
oneActor.name.setText(temp.get(position).getPackSize_packSize());
oneActor.cmtCount.setText(temp.get(position).getPackSize_sellingPrice());
((ViewGroup) container).addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public int getCount() {
return packsizedata.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
class OneActor{
// ImageView avatar;
TextView name,cmtCount;
}
}
By defaulat when i run the app it shows like this,in second pager it is not showing product price,
But when i scroll image it shows price
My Expected output is
This is my json response
http://pastebin.com/fbJang2B
First, as mentioned by #NotobharatKumar, you're loading the second adapter on a wrong event of the parent adapter, ie in onPageScrolled method. Second, you're setting a new adapter each time on that specific event, it seems useless. Finally, you are setting datas in an event, (I'm not sure why you're doing this but) I'd prefer to set it in a separate adapter and let the events listener for specific behaviors.
For me, it seems that you have two separate adapters, but one datas list shared by both. Assuming that, you have to set them both at start with their datas respectly, and on the event onPageSelected of the top adapter, you just have to automatically scroll the second. And if they have the same position in the list, onPageSelected should do the work correctly.
So, these modifications should solve your issue:
Your code in onScrollChanged, when you set the image and the text, seems really weird to me. I'd use a first adapter where I'll set all the datas like these two for the first ViewPager:
#Override
public void onCreate(Bundle savedInstansteState) {
...
// set a simple adapter for the first ViewPager
FirstPagerAdapter imageadapter =
new FirstPagerAdapter(ProductLandingActivity.this, categorylist);
pagerimages.setAdapter(imageadapter);
...
}
Then, as usual, set your datas in the FirstPagerAdapter:
#Override
public View getView(int position, View view, ViewGroup container) {
...
// set the content
Picasso.with(ProductLandingActivity.this)
.load(categorylist.get(position).getProductLanding_packLink())
.error(R.drawable.nopreview )
.placeholder(R.drawable.progress_animation)
.into(selectedImage);
selectedname.setText(
categorylist.get(position).getProductLanding_packDesc());
...
}
Then no need to (re)load the image or the text when an event is triggered, since they will be holding by the adapter.
You only use getPackSize_packSize() and getPackSize_sellingPrice() in the second adapter, so you should create a separate list to only fill with these datas but outside the swiping event. Start by initializing the second list and the adapters:
// get the Items to fill the pack items list (like you did for `temp`)
ArrayList<Items> packItems = new ArrayList<>();
for (int i = 0; i < categorylist.size(); i++) {
packItems.add(categorylist.get(position).getItems());
}
// fill the first adapter with your list of products (ie categorylist)
...
pagerimages.setAdapter(imageadapter);
...
// fill the second adapter with the pack items list
packadapter = new MyPacksPagerAdapter(ProductLandingActivity.this, packItems);
pagerpacks.setAdapter(packadapter);
You have to do this when categorylist is created and populated. So place this above code for example in your callback, when you retrieve the datas from your server.
Since the second list packItems is filling in the same order than categorylist, there will be no weird behavior by changing the both positions.
Now, in the second adapter, it's preferable to use the local list packsizedata, as follows:
#Override
public Object instantiateItem(ViewGroup container, int position) {
...
oneActor.name.setText(
packsizedata.get(position).getPackSize_packSize());
oneActor.cmtCount.setText(
packsizedata.get(position).getPackSize_sellingPrice());
...
}
Finally, control the bottom ViewPager by using onPageSelected event of the first:
pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) { }
#Override
public void onPageSelected(int position) {
// set the second current page regarding the position of the first
pagerpacks.setCurrentItem(position);
}
#Override
public void onPageScrollStateChanged(int state) { }
});
Hope this will be helpful.
You should implement code for action at onPageSelected method because after page changed of your ViewPager then onPageSelected will be called.
I am trying to implement a ViewPager without using Fragments. Let's say, I have an unspecific amount of data stored inside some kind of database which is mapped to specific days. What I am trying to do is to display the data for a specific day on ViewPager pages.
This is what I am currently doing:
#Override
protected void onCreate(Bundle savedInstanceState) {
dataList = (ViewPager) findViewById(R.id.dataList);
dataList.setAdapter(new DataListSlidePagerAdapter());
dataList.setCurrentItem(Integer.MAX_VALUE / 2);
}
private class DataListSlidePagerAdapter extends PagerAdapter {
int lastPosition;
#Override
public Object instantiateItem(ViewGroup collection, int position) {
if (position > lastPosition) {
GregorianCalendar today = Settings.getCurrentDay();
today.add(GregorianCalendar.DATE, 1);
Settings.loadData();
}
else if (position < lastPosition) {
GregorianCalendar today = Settings.getCurrentDay();
today.add(GregorianCalendar.DATE, -1);
Settings.loadData();
}
lastPosition = position;
LinearLayout v = createDataList(Settings.getTodaysData());
collection.addView(v);
return v;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
((ViewPager) collection).removeView((LinearLayout) view);
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
This actually almost works, but unfortunately, the data for the current day is never shown on the current page, but rather on the next or previous page. My guess would be, that this is because instantiateItem does not necessarily build the view of the current page, but rather of some page which is probably needed to be shown next. createListData(), however, always creates the view of the current day.
Any idea how to fix this and to display the right data on the right pages? You can assume, that there are also functions like Settings.getTomorrowsData() and Settings.getYesterdaysData().
Imagine there is a viewpager with 4 page, the 1st and 2nd page are storing the edittext, and the third one need to display the inputed data from 1st and 2nd page.
The problem is , viewpager pre-load the pervious page and next page , if I create the custom adapter like that:
Custom adapter
#Override
public Object instantiateItem(ViewGroup container, int position) {
rootView = (LinearLayout) LayoutInflater.from(ctx).inflate(pages[position], null);
if (position == 0) {
form1(rootView);
} else {
form2(rootView);
}
((ViewPager)container).addView(rootView);
return rootView;
}
Example function form2
private void form2(View rootView){
TextView previous_page = (TextView) rootView.findViewById(R.id.previous_page);
TextView next_page = (TextView) rootView.findViewById(R.id.next_page);
final EditText type = (EditText) rootView.findViewById(R.id.edit_type);
final EditText amount = (EditText) rootView.findViewById(R.id.edit_amount);
final Spinner period = (Spinner) rootView.findViewById(R.id.edit_period);
final EditText name = (EditText) rootView.findViewById(R.id.edit_name);
final EditText phone_no = (EditText) rootView.findViewById(R.id.edit_phone_no);
final EditText email = (EditText) rootView.findViewById(R.id.edit_email);
previous_page.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
viewPager.setCurrentItem(0, true);
top_bar.setImageResource(R.drawable.form_1_header);
}
});
next_page.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
String[] input_list = {amount.getText().toString(),name.getText().toString(),phone_no.getText().toString(),email.getText().toString()};
String invalid_msg = check_valid(input_list);
if (invalid_msg.equals("")) {
new FormHandler(ctx,formListener).execute(type.getText().toString(),amount.getText().toString(),period.getSelectedItem().toString(),name.getText().toString(),phone_no.getText().toString(),email.getText().toString());
} else {
Toast.makeText(ctx, invalid_msg ,Toast.LENGTH_LONG).show();
}
}
});
}
Then , for example if I enter the 1st page, it call the instantiateItem 2 times, the position are 0 and 1 , and it called the form1() and the form2(), which I expect only when I enter that page , it call the function of that page . e.g. At 1st page , run form1(), At 2nd page , run form2(). How to fix that? thanks.
Update (1):
It caused a problem, when I enter 2nd tab , it preload the 3rd tab, which call the form3(), so after I input the data in those edittext at 2nd tab, and go to the 3rd tab, it does not call form3() again, so the 3rd tab does not display enter data from 2nd tab (The view was preload and instantiate already)
Update (2):
The page is not a fragment , it is a layout and inflate at the adapter(named "rootview" and the pages array is:)
int[] pages = {R.layout.form_1,R.layout.form_2,R.layout.form_3,R.layout.form_4};
Update (3):
My whole viewpager
private class ViewPageAdapter extends PagerAdapter {
// Declare Variables
public int[] pages;
private LinearLayout rootView;
public ViewPageAdapter(int[] _pages) {
pages = _pages;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == (LinearLayout) object;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
rootView = (LinearLayout) LayoutInflater.from(ctx).inflate(
pages[position], null);
if (position == 0) {
form1(rootView);
} else if (position == 1) {
form2(rootView);
} else if (position == 2) {
form3(rootView);
} else if (position == 3) {
form4(rootView);
}
((ViewPager) container).addView(rootView);
return rootView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
}
#Override
public int getCount() {
return pages.length;
}
}
You can override the setPrimaryItem method in adapter. this method will give the current displaying object. This may help you.
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (mobject != object) {
mObject=objet; //mObject is global variable in adapter
//You can update your based on your logic like below
View view == (LinearLayout) object;
form4(view);
}
super.setPrimaryItem(container, position, object);
}
Then , for example if I enter the 1st page, it call the instantiateItem 2 times, the position are 0 and 1 , and it called the form1() and the form2(), which I expect only when I enter that page , it call the function of that page . e.g. At 1st page , run form1(), At 2nd page , run form2(). How to fix that? thanks.
you can not fix that, because of giving better UX to user setOffscreenPageLimit(0); dose not work and the default value is always 1 which means it always preloads your next and previous page.
so after I input the data in those edittext at 2nd tab, and go to the
3rd tab, it does not call form3() again, so the 3rd tab does not
display enter data from 2nd tab (The view was preload and instantiate
already)
you can save your data in SharedPreferaces and read them in onResume method of tab3. in this way you will get correct data from tab2 and tab1.
I would like to keep a list of 3 views at all times. The app starts at position 1 (out of positions 0,1,2). When someone scrolls to position 0, I would like to remove view 2, and create a view before position 0. This way, it appears to the user, that there are unlimited views. In the same way, when someone scrolls to position 2, I would like to remove the view at position 0 and add one at the end.
However I'm having problems with both adding and removing views. When I get to position 0, nothing changes unless I try scrolling past position 0 (to position -1, i.e. the boundary is hit). At that point, I can see that it is the boundary of my views, but then setCurrentItem(1,false) is triggered and I'm brought back to the middle of the views. When I scroll to position 2 I see that position 2 has been updated. However position 0 and 1 remain the same.
When I scroll to position 2, nothing happens. However if I try and scroll to the boundary, for some reason, position 0 gets updated and setCurrentItem(1,false) is triggered.
I have no idea why its happening like this. Can anyone shed some light on this?
Here is my code:
public class MainActivity extends Activity {
ArrayList<Integer> showThree = new ArrayList<Integer>();
int focusedPage = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showThree.add(0,5); //adding integers 5,6,7 for positions 0,1,2
showThree.add(1,6);
showThree.add(2,7);
final MyPagerAdapter adapter = new MyPagerAdapter(getApplicationContext(),showThree);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
myPager.setOnPageChangeListener(new OnPageChangeListener(){
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
//when position= 0, change the 3 views from 5,6,7 to 4,5,6.
if (focusedPage == 0) {
showThreeMonths.set(0,4);
showThreeMonths.set(1,5);
showThreeMonths.set(2,6);
adapter.notifyDataSetChanged();
adapter.startUpdate(myPager);
}
else if (focusedPage ==2){
//ignore, just testing focusPage=0 for now }
}
//set current page to the middle of the 3 new views, which would be
//the same view at position 0 of the old 3 views.
//Thus user doesn't experience the views changing despite being 3 new views.
myPager.setCurrentItem(1,false);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int position) {
focusedPage = position;
}
});
}
PagerAdapter
public class MyPagerAdapter extends PagerAdapter {
private ArrayList<Integer> showThreeMonths;
private Context ctx;
public MyPagerAdapter (Context ctx, ArrayList<Integer> showThree){
this.ctx = ctx ;
this.showThree = showThree;
}
#Override
public int getCount() {
return showThree.size();
}
public Object instantiateItem(ViewGroup collection, int position ){
//NewCustomView is a class I made that takes parameters context and an integer and creates a view based on the integer
NewCustomView MyOwnView = new NewCustomView(ctx, showThree.get(position));
View customViewLayout = MyOwnView.newLayout; //part of the class object
collection.addView(customViewLayout);
return customViewLayout;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object arg2) {
((ViewPager) collection).removeView((ViewGroup) arg2);}
#Override
public Parcelable saveState() {
return null;}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;}
#Override
public void startUpdate(ViewGroup collection) {}
#Override
public void finishUpdate(ViewGroup collection) {}
}
The instantiateItem() method creates the 2 view pages in the memory by default. Therefore when you swipe to the second page then 0 page is recreated as it's outside the range of the 2 pages saved in the memory. Please try to use
myViewPager.setOffscreenPageLimit(numberOfPages)
method that receives an integer as a parameter and declares how many pages it should be keeping before recycling them.