I want to use ViewPager to swipe through songs in my Media Player ( going forward or backward like SoundCloud app for android ).
I have created a fragment which I inflate and set inside information like song name, author etc.
I have read some topics on StackOverflow about the ViewPager displays wrong items ( Viewpager shows wrong page and Android Viewpager show wrong pages and a lot more ) but I don't have fixed number of XML's to use with the ViewPager.
My Data is displayed in a RecyclerView, when I am pressing the item on index 4 for example I call the following method:
mPager.setCurrentItem(position);
So the mPager is set to position 4.
#Override
public Fragment getItem(int position) {
Log.i("TEST","Returnin a fragment on position "+position);
return fragment = new MusicSliderFragment();
}
But on my Adapter when it's changing I get the following output:
02-23 10:39:57.131 7599-7599/************** I/TEST: Returning a fragment on position 4
02-23 10:39:57.131 7599-7599/************** I/TEST: Returning a fragment on position 3
02-23 10:39:57.131 7599-7599/************** I/TEST: Returning a fragment on position 5
And when my application is launched I get the following output:
I/TEST: Returning a fragment on position 0
I/TEST: Returning a fragment on position 1
The main problem is when I am opening the app first time and set the song on position 4 the fragment information are displayed in the next fragment.
Is there a way how can I fix this?
Adapter Class:
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private MusicSliderFragment fragment;
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Log.i("TEST","Returnin a fragment on position "+position);
return fragment = new MusicSliderFragment();
}
#Override
public int getCount() {
return NUM_PAGES;
}
public void updateSongName(String name) {
fragment.setSongName(name);
}
}
The RecyclerView Listener :
musicList.addOnItemTouchListener(
new RecyclerItemClickListener(getApplicationContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, final int position) {
Log.i("TEST", "Setting the mPager position to: "+position);
mPager.setCurrentItem(position);
}
}
And the `Listener` of the `mPager` where I update the song name for example:
mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
play(position);
ScreenSlidePagerAdapter adapter = (ScreenSlidePagerAdapter) mPager.getAdapter();
adapter.updateSongName(splitName(myDataList.get(position))[0]);
Log.i("TEST", "Playing the position: "+position);
mPagerAdapter.notifyDataSetChanged();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
View pager can load neighbour fragments when you choose current. Method setOffscreenPageLimit() defines how many neighbours you want to preload.
onPageSelected(int position) should work correct and returns current item.
Viewpager indexing starts from zero if you want to load the fourth Fragment then you need to set the position 3.
mPager.setCurrentItem(3); //Load the 4th fragment according to you.(0-3)
Related
I'm building a mvp application and i'm looking for a clean and correct way to communicate between main fragment and those viewpager's fragments.
More specific,I have :
JobsFragment : fragment which contains the view pager
NewJobsFragment : one of view pager fragments
RecommendedJobsFragment : 2rd fragment from viewpager
SavedJobsFragment : 3rd fragment from viewpager
All viewpager's fragments contain a RecyclerView.Each part uses a MVP structure ( JobsFragment has it's own JobsPresenter,NewJobsFragments has NewJobsPresenter and so on)
User can filter the jobs in each fragment.Those filters can be selected from JobsFragment and when this is happening I need to 'notify' current fragment from viewpager and send those filters to it.
I read about child-parent relationship between presenters but I don't think it's a proper solution.
I also think about sharing a single presenter for all fragments but it's a lot of code and I guess it won't be readable anymore.
Thanks in advance !
Create one Abstract Fragment that extends Fragment. like this
public abstract BaseChildFragment extends Fragment{
public void onViewPagerPagechange();
}
Make All ChildFragment (JobsFragment , NewJobsFragment etc) extends BaseChildFragment ,
On your Pager Adapter add below code.
LruCache<Integer,BaseChildFragment> cache = new LruCache<>(4);
public BaseChildFragment getBaseChildFragment(int position){
return cache.get(position);
}
#Override
public Fragment getItem(int position) {
if (position == JOB) {
if (Utility.isEmpty(fragmentProfile)) {
jobsFragment = JobsFragment .getInstance()
cache.put(position,fragmentProfile );
}
return jobsFragment ;
} else if (position == NEW_JOB) {
......
return fragment;
}
return null;
}
On your Fragment that has View pager AddpageChangeListener like this
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
// optional
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
// optional
#Override
public void onPageSelected(int position) {
BaseChildFragment fragment = adapter.getBaseChildFragment(position);
fragmeng.onViewPagerPagechange();
//you can invoke presenter logic by calling this method on each child presenter will call your filter (its upto you)
}
// optional
#Override
public void onPageScrollStateChanged(int state) { }
});
This will call your ChildFragment onViewPagerPagechange(); method. where you can load your child fragments. you can also get currently selected fragment. and simplest way is that you can get current fragment and check with getInstance();
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
}
});
I have a simple FragmentPagerAdapter class
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
PagerAdapter fragment;
fragment = new PagerAdapter(data.get(position));
return fragment;
}
#Override
public int getCount() {
return data.size();
}
#Override
public CharSequence getPageTitle(int position) {
if (position >= pageCount || position < 0)
return null;
return "Profile " + position;
}
}
that PagerAdapter is a simple class that extends from Fragment.
and then I set an instance of SectionsPagerAdapter to my ViewPager.
problem is here:
at first I have one page on view pager( data size is 1), when I get more data , I call SectionsPagerAdapter instance notifyDataSetChanged, but in getItem(int position) method instead of position start form 0, it start from 1 !!!
what should I do? I wanna re creat my view pager after I get more data
Thanks in advance
You needed to set the adapter again to reset the position, notifyDataSetChanged is used to notify the adapter there is a data change so its rebind the data only.
Or You can use this code to move the view pager which position you want
viewPager.setCurrentItem(0);
notifyDataSetChanged() will only Notify any registered observers that the data set has changed regardless of touching viewPager positions.
There are two different classes of data change events, item changes
and structural changes. Item changes are when a single item has its
data updated but no positional changes have occurred. Structural
changes are when items are inserted, removed or moved within the data
set.
You have to set viewPager's position to start page(position 0) manually after notifyDataSetChanged().
adapter.notifyDataSetChanged();
viewPager.setCurrentItem(0);
I'm beginner with Android, I use ViewPager with 5 fragments, and I want to show position of fragment like "1 of 5" when fragment 1 is selected, how can I do?
Thanks for all support!
you can use viewPager.getCurrentItem() to get current item of view pager and then you can use that value in textview as
textview.setText(viewPager.getCurrentItem()+" of 5");
You can add to your ViewPager a OnPageChangeListener, onPageSelected(int position) will be called when a page is shown in the pager. There you should access your pages indicator in order to change the current page.
addOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Not used
}
#Override
public void onPageSelected(int position) {
// Logic to update indicator labels.
}
#Override
public void onPageScrollStateChanged(int state) {
// Not used
}
});
You can have a textview at the bottom of your viewpager and there you can use
viewpager.getCurrentItem();
If you have a pageListener attached to your viewpager , you can also change it in onPageSelected method.
I am new in view pager.i have 2 pages in view pager..each contain button..i want show button after page fully loaded.Now button is showing half scrolling it self..
class MyPageAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
adding page
pageAdapter = new MyPageAdapter(activity.getSupportFragmentManager(), fragments);
viewpager.setId(position);
viewpager.setAdapter(pageAdapter);
fragment:
public class secondclass extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_secondclass, container, false);
return rootView;
}
please help me..
Just use OnPageChangeListener.
Actually you need to do some tedious tasks for this to be done.
First your activity containing the viewpager should have a way to communicate with the fragments of the viewpager. once you are done with this.
Then set OnPageChangeListener on the viewpager
and one of the overridden callback methods would be the following one
#Override
public void onPageSelected(int position) {
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}
you are all set to go
In viewpager nearby fragments are loaded even before they are actually visible to the user, this is done so as to provide a smooth scrolling effect.
So onCreateView and onResume methods of the second fragment is already called...even before the user scrolls to the second page.
So to do something exactly when the user loads a page completely we need to user the onPageSelected method and with the help of the position argument we notify the fragment at that position to do the tasks.
In your case show the button...so you need to set visibility of the button INVISIBLE in the layout and make it visible when the page is selected.
For the above thing to work your activity needs to have a reference to the fragment...or you may request the viewpageradapter to provide you the reference by doing a bit caching of the current fragment...apply any logic of yours.
Assume all your fragments are of type MyFragment and have a method doVisibleTask to make the button visible AND getItem of your view pager adapter returns the same instance everytime for a particular position.
you could do the following
#Override
public void onPageSelected(int position) {
// get the reference to the fragment
MyFragment mf = (MyFragment)viewPagerAdapter.getItem(position);
mf.m=doVisibleTask();
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}