I have a ViewPager which contains Fragments. I want to add fragment before and after the current position. Here is the adapter:
public class TabManager extends FragmentStatePagerAdapter {
private final List<Fragment> list = new ArrayList<>();
public TabManager(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public int getItemPosition(Object object) {
if (object instanceof Fragment) {
return list.indexOf(object);
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
return list.get(position);
}
#Override
public int getCount() {
return list.size();
}
public void addFragment(Fragment fragment, int index) {
list.add(index, fragment);
notifyDataSetChanged();
}
}
I can add fragments after my position (it appears in the right side of the ViewPager) without problem. But if I add a new fragment before my position (the left side of the ViewPager) the ViewPager immediately focuses to it.
So, my question is, how to add a new fragment to the left side, without focusing (so let the user swipe) to it?
In other words here is one fragment:
1
^
I add a new one to the right side, but the first one stay focusing:
1 2
^
Now, if I add another one to the left side it immediately focuses (that's what I don't want):
3 1 2
^
This should be the correct:
3 1 2
^
Sorry, can not comment due to a reputation.
If your current position is 0 (1 fragment), and you add fragment to the right side, current item position dos not change - 0 (added fragment does not change it).
But if you add the fragment to the left side (before fragment 1), your adapter's current position still 0, and the fragment on that position is now fragment 3, NOT fragment 0 anymore. So i guess before notifying adapter you should change your adapter's position to the desired item (if you add after - do nothing, if you add before - correct you position).
In you case - set current adapter position to 1 instead of 0.
Related
I have one fragment where are three (default) images and when user click on them, they will change to another. But when i swipe to another fragment and back to fragment with images there are not default ones as on the start. When I swipe two times so I will pass to another fragment (distance from original with images is 2 fragments) images are resetted to default. I was trying to implement setOffscreenPageLimit() from ViewPager and set it to 1, but minimum "length" when views in fragments are resetted is 2. How can I change that images to default manually after swipe action? Thank you.
Edit: I think that issue why onResume() is not working here: Fragment onResume not called
but i dont know what that means :/ I have three classes FragmentController.class, PagerAdapter.class and class of specific fragment for example FirstFragment.class. I don't know how connect these classes together.
Check that you create the fragments in the getItem() method of the adapter and do not hold any reference to that fragments (only WeakReferences if necessary), otherwise the fragments could not be destroyed.
EDIT:
The first fragment is unloaded only when you are in the third one because setOffscreenPageLimit is at least 1 so a viewpager allways loads the fragments that are at both sides of the selected one.
What you could do is to update your adapter with this code to provide a getFragment(position) method:
private HashMap<Integer, WeakReference<Fragment>> fragmentReferences = new HashMap<>();
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = FirstFragment.newInstance();
break;
// etc
}
fragmentReferences.put(position, new WeakReference<>(fragment));
return fragment;
}
public Fragment getFragment(int position) {
WeakReference<Fragment> ref = fragmentReferences.get(position);
return ref == null ? null : ref.get();
}
After then you can get the selected fragment and call the method you want from the first fragment when a page is selected:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int currentPage) {
if (currentPage == 0) {
Fragment firstFragment = adapter.getFragment(0);
if (firstFragment != null) {
// This method resets the images of the first fragment
((FirstFragment) firstFragment).reset();
}
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Do nothing
}
#Override
public void onPageScrollStateChanged(int state) {
// Do nothing
}
});
What I want:
I have been trying to implement two directional Endless viewpager in Android, Left to Right & Right to Left
What I did:
I have implemented Endless viewpager adapter, it works fine for right to left direction, I have set current item position by viewPager.setCurrentItem(Integer.MAX_VALUE/2);.
Reference:
Help would be appreciate.
Try to check below FragmentPagerAdapter to get endless viewpager adapter :
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Fragment getItem(int position) {
return getFragmentBasedOnPosition(position);
}
private Fragment getFragmentBasedOnPosition(int position) {
int fragmentPos = position % 3; // Assuming you have 3 fragments
switch(fragmentPos) {
case 0:
return Fragment1.newInstance();
case 1:
return Fragment2.newInstance();
case 2:
return Fragment3.newInstance();
}
}
}
I found solution here.
I hope its helps you.
I have made my own solution. I created a ViewPager that supports infinite looping effect, smart auto-scroll, compatible with any indicators and easy to use. It especially uses it as banners of application with a simple item page.
My custom ViewPager can:
Plug and play, easy to use
Infinite Looping items
Auto-scroll items, allow config, auto-resume/pause when activity/fragment resume/pause
Won't scroll or loop if it has only 1 item
Compatible with many indicators
Github link: https://github.com/kenilt/LoopingViewPager
Hope it helps!
One simple way to achieve this for ViewPager2 is with 3 basic ideas:
Add the first and last items of your data model collection to the end and start, respectively, of that same collection. E.g. listOf(1, 2, 3, 4, 5) should become listOf(5, 1, 2, 3, 4, 5, 1).
When setting up the pager, set it to start with index 1.
When the user scrolls to index 0, have the pager scroll instantly to the penultimate index. When the user scrolls to the last index, have the pager scroll instantly to index 1.
Some sample code to do this is as follows:
1.
private fun <T> List<T>.prepareForTwoWayPaging(): List<T> {
val first = first()
val last = last()
return toMutableList().apply {
add(0, last)
add(first)
}
}
pager.setCurrentItem(1, false)
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
// We're only interested when the pager offset is exactly centered. This
// will help create a convincing illusion of two-way paging.
if (positionOffsetPixels != 0) {
return
}
when (position) {
0 -> pager.setCurrentItem(adapter.itemCount - 2, false)
adapter.itemCount - 1 -> pager.setCurrentItem(1, false)
}
}
})
Caveat: this code does not reconcile any TabLayout or an empty data model collection.
The proposed solutions are correct but to achieve the result you need to set the initial value of your viewpager to Integer.MAX_VALUE/2.
Anyway, I don't really like this solution, setting getCount to return Integer.MAX_VALUE can have huge impact on application performance.
I figured out a solution in order to avoid this problem using the:
onPageScrollStateChanged Listener
I simply reorder the fragment list, update the viewPager and move to the new page without animation, the result is an endless loop in both directions:
mainViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener()
{
Boolean first = false;
Boolean last = false;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{}
#Override
public void onPageSelected(int position)
{
if (position == 0)
{
first = true;
last = false;
}
else if (position == mainFragmentList.size() -1)
{
first = false;
last = true;
}
else
{
first = false;
last = false;
}
}
#Override
public void onPageScrollStateChanged(int state)
{
if (first && state == ViewPager.SCROLL_STATE_IDLE)
{
// Jump without animation
Fragment fragment = mainFragmentList.get(mainFragmentList.size() -1);
mainFragmentList.remove(mainFragmentList.size() -1 );
mainFragmentList.add(0,fragment);
mainPagerAdapter.setData(mainFragmentList);
mainPagerAdapter.notifyDataSetChanged();
Log.e(TAG,mainFragmentList.toString());
mainViewPager.setCurrentItem(1,false);
}
if(last && state == ViewPager.SCROLL_STATE_IDLE)
{
// Jump without animation
Fragment fragment = mainFragmentList.get(0);
mainFragmentList.remove(0);
mainFragmentList.add(fragment);
mainPagerAdapter.setData(mainFragmentList);
mainPagerAdapter.notifyDataSetChanged();
Log.e(TAG,mainFragmentList.toString());
mainViewPager.setCurrentItem(mainFragmentList.size()-2,false);
}
}
});
This is what happens here:
in this example, we have 4 fragments A-B-C-D
if the user is on fragment A (first), the new List will become: D-A-B-C
[remove the last and push as first]
I update the ViewPager and move (without animation) again to fragment A so index 1.
Now the user can continue to scroll left and will find fragment D.
Same thing with the last fragment:
starting again with A-B-C-D
if the user is on fragment D (last), the new List will become: B-C-D-A
[remove the first and push as last]
I update the ViewPager and move (without animation) again to fragment D so index mainFragmentList.size()-2.
Now the user can continue to scroll right and will find fragment A.
Remember to implement FragmentStatePagerAdapter NOT FragmentPagerAdapter
I have built an application with a navbar on the left, whose main content is a ViewPager
The ViewPager slides between two different views.
When the user selects something from the navgation bar, I send a message to the ViewPager's adapter (I have tried both FragmentPagerAdapter and FragmentStatePagerAdapter for this, both won't work) which sets an internal variable and calls notifyDatasetChanged();
The problem is that the getCount() method always returns 2 , so when the adapter checks to see if the dataset has changed, it sees that the items are still 2 and does not go on to call getItem(position).
The getItem(position) returns different fragments according to the value of the internal variable that is set before notifyDatasetChanged();
I tried overriding getItemId(position) in case the pager checks for the id, but it seems to not bother after checking the count.
Is there a way to force the adapter to rebuild the fragments when notifyDatasetChanged() is called?
Thanks in advance for any help you can provide
Edit: here is the code I am currently using:
public class ContentAdapter extends FragmentPagerAdapter {
private ViewedSection _section = ViewedSection.Main;
public ContentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ViewedScreen screen = get_screen(position);
return screen == null ? null : FragmentFactory.GetFragment(get_screen(position));
}
#Override
public int getCount() {
return 2;
}
private ViewedScreen get_screen(int position) {
//code to resolve which screen will be shown according to the current position and _section
}
public void set_page(ViewedSection section) {
this._section = section;
notifyDataSetChanged();
}
}
So when the user clicks on a NavBar item, I call ((ContentAdapter)_pager.getAdapter()).set_page(section);
For this, you need to override
public int getItemPosition (Object object)
Return POSITION_UNCHANGED if you don't want to replace the fragment. Return POSITION_NONE if you want to replace the fragment. (Also, you can return a new position to move the fragment to.)
A common override is
public int getItemPosition (Object object) {
return POSITION_NONE;
}
which will just rebuild everything.
I'm trying to implement 3 slides composed of 3 fragments (or 3 layouts) with ViewPager and I want to know which slide I currently show in order to display the appropriate content. In simpler words, I want content 1 on slide 1, content 2 on slide 2 and so on.
Here is my actual code from my Activity (from android official doc) :
public class SliderActivity extends FragmentActivity {
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slider);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
#Override
public void onBackPressed() {
if (mPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
// A simple pager adapter that represents 3 ScreenSlidePageFragment objects, in sequence.
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
System.out.println("POSITION = " + position); // Or mPager.getCurrentItem()
return new SlidesFragment();
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
No matter how hard I try, getCurrentItem() or position still prints wrong values. Only the second slide sends a number to the console. It's 2 when I swipe right and 0 when swipe to left.
What am I doing wrong ?
You definitely want to use the position value in getItem, do not call getCurrentItem because the pager will create items that are not the current item and not on-screen. The pager has an offscreen page limit of 2 by default. So, when it is first created, getItem will be called with a position of 0 and then again immediately with position of 1. The fragment at position 1 is not the current item, but it is being created offscreen so the user can start sliding over and see it. Then when you complete the swipe, getItem is called with a position of 2 to pre-load the next and final slide.
To accomplish "I want content 1 on slide 1, content 2 on slide 2", your SlidesFragment needs to take an argument (store in the arguments bundle) the position that tells it which content to display. Or, more likely, you have 3 different fragment types you would create based on the position.
I have a problem with restoring proper ViewPager page after orientation change.
CONCEPT:
I've got ViewPager which is set for 8 pages initially.
It has OnPageCHangeListener and when user reaches 5th page, I fetch data from server and add another 8 pages to ViewPager.
So every 7 pages I add another 8 pages, it makes it endless. This is listener code:
ViewPager mPager = (ViewPager) findViewById(R.id.frame);
mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
if(arg0 % 7) {
addPagesToViewPagerAdapter(isMainPage);
}
}
This is my ViewPager adapter code:
public class FullImageAdapter extends FragmentStatePagerAdapter {
private static ArrayList<Main> response;
public FullImageAdapter(android.support.v4.app.FragmentManager fm,
ArrayList<Main> resp) {
super(fm);
response = resp;
}
#Override
public int getCount() {
return response.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
#Override
public Fragment getItem(int position) {
return FullImageFragment.newInstance(response.get(position));
}
public void addPages(ArrayList<Main> resp) {
response.addAll(resp);
}
}
I also has onConfigurationChaged in Activity which holds ViewPager fragment.
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
PROBLEM:
Problem is with showing proper ViewAdapter page when orientation change.
For example:
At the beggining there are 8 pages in the ViewPager.
User scrolles to the 3rd page, then change orientation and 3rd page is shown. Everything works fine.
Problem is when uses scrolls after 8 pages f.ex to the 9th (at that time there are 16 pages, because at page 7th we added 8 more) when he change orientation at 9th, the 8th page is shown, which is bad.
To sum up:
Changing orientation:
At position 3 - shown is 3 - good
At position 7 - shown is 7 - good
At position 8 - shown is 8 - good
At position 9 - shown is 8 -BAD
At position 10 - shown is 8 - BAD
etc.
It seems like whatever is handling orientation changes doesn't know that I added more pages and it's showing the last page that was in the adapter at the beggining.
This only affects changing orientation, when user scrolls "normally" everything is fine.
I also use diffrent layouts for land and horizontal orientation.
How can I fix this?