I'm using Android Studio 1.2.1.1 and I've implemented a ViewPager with a fair few fragments. I'm trying to get the data from each of the fragments into a public class via an interface. The classa nd interface are not the issue though, I want to export the data from the current page before a new page is selected, unfortunately it appears these are the only options and none really suit my needs:
_mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrollStateChanged(int position) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageSelected(int position) {
}
});
I would be able to invoke the call to export the data easily on checkboxes etc, but my fragments include imageviews (exported as base64) and edittexts. Has anyone else come across this in the past? Is it an easy one or is it going to be changing my code entirely?
Any help would be greatly appreciated
Can you not just keep a reference to what page you're currently on and set that index every time onPageSelected is called. You can then see what the index was before changing it and update the old Fragment with that?
Example:
int oldPosition = -1;
#Override
public void onPageSelected(int position) {
if (oldPosition != -1) {
extractInformationFromFragment(oldPosition);
}
oldPosition = position;
}
private void extractInformationFromFragment(int position) {
switch (position) {
case 0:
fragment1.getInformation();
break;
case 1:
fragment2.getInformation();
break;
case 2:
fragment3.getInformation();
}
}
I'm handling it this way :
private int mCurrentSelectedPage = 0;
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout) {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// Get data from last selected fragment
EditFragment oldFragment = getFragment(mCurrentSelectedPage);
if (oldFragment != null) {
oldFragment.getData();
}
// Set data to the new fragment
EditFragment newFragment = getFragment(position);
if (newFragment != null) {
newFragment.setData();
}
mCurrentSelectedPage = position;
}
});
public EditFragment getFragment(int position) {
String fragmentTag = "android:switcher:" + mViewPager.getId() + ":" + position;
return (EditFragment) getSupportFragmentManager().findFragmentByTag(fragmentTag);
}
Related
I am using a ViewPager with single fragment instance in which I am showing Media files like Images, Videos, Audio.
I have implemented ExoPlayer for handling Video & Audio files. And Glide for images.
To avoid the memory leaks I am releasing the ExoPlayer object like this in ItemViewerFragment.java :
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
trackSelector = null;
simpleExoPlayerView.setPlayer(null);
}
}
#Override
public void onStart() {
super.onStart();
if (player == null && currentGalleryModel != null) {
initializePlayer();
}
}
#Override
public void onResume() {
super.onResume();
if (player == null && currentGalleryModel != null) {
initializePlayer();
}
}
#Override
public void onPause() {
super.onPause();
releasePlayer();
}
#Override
public void onStop() {
super.onStop();
releasePlayer();
}
And in onViewCreated() I am initializing view like this :
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (currentGalleryModel.isVideo() || currentGalleryModel.isAudio()) {
simpleExoPlayerView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
initializePlayer();
} else if (currentGalleryModel.isImage() || currentGalleryModel.isGif()) {
simpleExoPlayerView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
Glide.with(this)
.load(currentGalleryModel.getFilePath())
.placeholder(android.R.color.black)
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
}
}
I am using FragmentStatePagerAdapeter. This is the getItem method :
#Override
public Fragment getItem(int position) {
return ItemViewerFragment.newInstance(mItems.get(position));
}
I am not able to detect the onPause of the fragment on first swipe of Viewpager. On second swipe video/audio files are stopped playing.
In activity I have tried adding .addOnPageChangeListener :
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
try {
((ItemViewerFragment)mAdapter.getItem(mPreviousPos)).imHiddenNow();
} catch (Exception e) {
e.printStackTrace();
}
mPreviousPos = position;
}
And in ItemViewerFragment.java :
public void imHiddenNow(){
releasePlayer();
}
Still video/audio keeps on playing.
Here is a Video Link to the Screencast.
Demo project GitHub link.
I followed an approach of maintaining HashMap of fragment objects inside PagerAdapter
Declare an interface :
interface FragmentLifecycle {
void onPauseFragment()
}
Implement an interface in Fragment.
public void onPauseFragment() {
if (simpleExoPlayer != null){
simpleExoPlayer.setPlayWhenReady(false);
}
}
Store all the fragment objects in a HashMap<Integer,Fragment> with there respective positions as a key. Declare hashmap inside PagerAdapter. Also declare one getter method for accessing fragment objects from hashmap. e.g.
#Override
public Fragment getItem(int position) {
ItemViewerFragment fragment = ItemViewerFragment.newInstance(mItems.get(position));
mFragments.put(position,fragment);
return fragment;
}
public ItemViewerFragment getMapItem(int position) {
return mFragments.get(position);
}
In activity where you have declared viewPager keep one variable currentPosition and implement ViewPager.OnPageChangeListener.
Inside onPageSelected method,
#Override
public void onPageSelected(int position) {
if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
currentPosition = position;
}
Edit from the future: you should never hold a reference to Fragment instances directly inside a FragmentPagerAdapter, because it can cause crashes after process death.
Here is the code for the pager adapter:
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Here is the scroll Listener:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Stop media here.
}
#Override
public void onPageSelected(int position) {
//Save your previous position here.
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
For the media you can use a for Loop and add all the fragments to the list at once and then use this for efficiency :
viewPager.setOffscreenPageLimit(3);
This will make sure only 3 instances of your fragment are available which is enough.
For using single fragment i would suggest you to do it like this:
public MyFragment() {
}
//This is to send a file to the fragment if you need it.
public static MyFragment newInstance(File file) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("file", file);
fragment.setArguments(bundle);
return fragment;
}
Then in the onCreate of Fragment you can retrieve your file like this:
File file;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
file = getArguments().getSerializable("file");
}
Now add your fragments to pager like this:
for (int i = 0; i < totalFiles; i++) {
viewPagerAdapter.addFragment(MyFragment.newInstance(fileList.get(i));
}
Hope this helps.
It's been more then a year since I used Exoplayer & I kinda tackled a similar problem. Please note that the APIs have changed a little bit so take the following code just to get an idea on how to implement a potential solution. Please let me know if it doesn't work, I'll look into the APIs further and get back to you.
Coming to the solution:
private int mPlayerCurrentPosition;
private int getCurrentPlayerPosition() {
return mExoPlayer.getCurrentPosition();
}
// call this from onPause
private void releaseExoplayer() {
mPlayerCurrentPosition = getPlayerCurrentPosition();
mExoPlayer.setPlayWhenReady(false);
mExoPlayer.release(); // this will make the player object eligible for GC
}
private void resumePlaybackFromPreviousPosition(int prevPosition) {
mExoPlayer.seekTo(mPlayerCurrentPosition );
}
The problem is that onPause and onResume are not called when the fragment visibility changed in ViewPager.
The solution is to add 2 visibility events: losingVisibility and gainVisibility.
Why is it a great solution?
Because you keep the framework managing Fragment cache and lifecycle.
We just add the callbacks needed to pause and resume our media in our fragment.
Step-by-step:
The below code is just an explanation for my code. Check Step*.java classes to see full implementation.
Create losingVisibility and gainVisibility methods in YourFragment.java:
public class YourFragment extends Fragment {
/**
* This method is only used by viewpager because the viewpager doesn't call onPause after
* changing the fragment
*/
public void losingVisibility() {
// IMPLEMENT YOUR PAUSE CODE HERE
savePlayerState();
releasePlayer();
}
/**
* This method is only used by viewpager because the viewpager doesn't call onPause after
* changing the fragment
*/
public void gainVisibility() {
// IMPLEMENT YOUR RESUME CODE HERE
loadVideo();
}
}
Call losingVisibility and gainVisibility every time a new page is selected (onPageSelected) in YourActivity.java:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
YourFragment cachedFragmentLeaving = mYourPagerAdapter.getCachedItem(mCurrentItem);
if (cachedFragmentLeaving != null) {
cachedFragmentLeaving.losingVisibility();
}
mCurrentItem = position;
YourFragment cachedFragmentEntering = mYourPagerAdapter.getCachedItem(mCurrentItem);
if (cachedFragmentEntering != null) {
cachedFragmentEntering.gainVisibility();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Add getCachedItem to YourPagerAdapter.java:
The 3rd step is adding a method to retrieve cached fragments. To do it we must cache a reference to a fragment created (overriding instantiateItem) and release the same reference (overriding destroyItem).
public class YourPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<YourFragment> mFragmentsHolded = new SparseArray<>();
#NonNull
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object fragment = super.instantiateItem(container, position);
if(fragment instanceof StepFragment) {
mFragmentsHolded.append(position, (StepFragment) fragment);
}
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
mFragmentsHolded.delete(position);
super.destroyItem(container, position, object);
}
public YourFragment getCachedItem(int position) {
return mFragmentsHolded.get(position, null);
}
}
i know its so late but i hope it helps anyone ...
viewPager keeps offscreen fragment started ..
so you have:
prev offscreen fragment (start) --> visible fragment (working)--> next offscreen fragment (start)
solution is you can override Fragment.setUserVisibleHint() and handle play/pause video..
This is what I want according to picture
I am sharing my code
Model Class
class Msg{
private Boolean BtnName;
public Boolean getBtnName() {
return BtnName;
}
public void setBtnName(Boolean btnName) {
BtnName = btnName;
}
}
AdapterClass:
#Override
public void onBindViewHolder(final TeachersTodayScheduleViewHolder holder, final int position) {
mTeacherScheduleArrayList.get(position).setBtnName(false);
holder.btn_checkin.setTag(position);
holder.btn_checkin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Object data = v.getTag();
if (data != null) {
// I have set the Boolean value to true after clicking this particular button, but whn the arraylist again loading, the boolean value disaapear
mTeacherScheduleArrayList.get(position).setBtnName(true);
if(mFlagtrue!=null && mFlagtrue.contentEquals("flag_true"))
{
if(mTeacherScheduleArrayList.get(position).getBtnName().equals(true))// when it return true, then below colde work, this is not happening
{
holder.btn_checkin.setText("Attended");
holder.btn_checkin.setBackgroundResource(R.color.green_btn);
holder.btn_checkin.setEnabled(false);
}
}
}
What am I doing wrong, please help,
Thanks in advance
public void onBindViewHolder(final TeachersTodayScheduleViewHolder holder, final int position) {
mTeacherScheduleArrayList.get(position).setBtnName(false); // <<<<<<
.......
You're setting the value to false every time the view is bound. Remove this line.
I want to create ViewPager and each page will contain unique data (for example simple textview with integers from 1 to 1000000) and I want to make user see first page with number 500000 (so he will be in the middle of viewpager). I tryed using FragmentStatePagerAdapter with overrided method
getCount() {
return 500000
} and
getItem(int position) {
return MyCustomFragment.newInstance(position)
}
and then set my viewpager.setCurrentItem(50000, false) but when I start app it crashes with OutOfMemory exception. But if I remove viewpager.setCurrentItem(50000, false) then everything works nice, but user starts with first page, not with 50000. As I understand OutOfMemory is thrown because when I set current item to 50000 fragmentadapter tryes to load every fragment from 1 to 50000 and keeps them in memory. So how can I avoid such problem? Probably I should find another solution, but I don't know how to solve my problem with some other way
UPD: here is code that doesnt work:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one_day_schedule);
mViewPager = (ViewPager) findViewById(R.id.day_page);
mViewPager.setAdapter(new SchedulePagerAdapter(getSupportFragmentManager()));
// Puts user in the middle of viewpager
mViewPager.setCurrentItem(Integer.MAX_VALUE/2, false);
}
public static class SchedulePagerAdapter extends FragmentStatePagerAdapter {
public SchedulePagerAdapter(FragmentManager fm) {
super(fm);
}
// This doesnt work even with empty fragment
#Override
public Fragment getItem(int position) {
return new Fragment()
}
#Override
public int getCount() {
// Makes viewpager almost infinite
return Integer.MAX_VALUE;
}
}
Try to override these methods:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public CharSequence getPageTitle(int position) {
String title = mTitleList.get(position % mActualTitleListSize);
return title;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int virtualPosition = position % mActualTitleListSize;
return super.instantiateItem(container, virtualPosition);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
int virtualPosition = position % mActualTitleListSize;
super.destroyItem(container, virtualPosition, object);
}
I made this class:
public class My_ViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<My_Fragment> fragments=new ArrayList<>();
private ArrayList<String> tabTitles=new ArrayList<>();
public My_ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getItemPosition(Object object) {
for (int i=0;i<fragments.size();i++){
Class<? extends My_Fragment> clazz= fragments.get(i).getClass();
if (clazz.isInstance(object)){
return i;
}
}
return POSITION_NONE;
}
#Override
public int getCount() {
return fragments.size();
}
public void addFragment(My_Fragment fragment, String tabTitle){
fragments.add(fragment);
tabTitles.add(tabTitle);
notifyDataSetChanged();
}
public void removeFragment(int position){
fragments.remove(position);
tabTitles.remove(position);
notifyDataSetChanged();
}
public My_Fragment getFragmentAtTab(int position){
return fragments.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitles.get(position);
}
}
I'm adding/removing fragments/tabs dynamically, and i want to save their state through configuration changes.
I implemented onSaveInstanceState():
int tabs=tabLayout.getTabCount();
for (int i=0;i<tabs;i++){
outState.putInt("TabsCount",tabs);
outState.putString("Tab_"+i,getTabTitle(i));
outState.putString("FragmentClassAtTab_"+i,my_viewPagerAdapter.getFragmentAtTab(i).getClass().getName());
}
and i implemented onRestoreInstanceState():
for (int i=0;i<tabs;i++){
try {
My_Fragment fragment= (My_Fragment) Class.forName(savedInstanceState.getString("FragmentClassAtTab_"+i)).newInstance();
addTab(fragment ,savedInstanceState.getString("Tab_"+i));
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
addTab method is:
public void addTab(My_Fragment fragment, String title){
if (fragment!=null) my_viewPagerAdapter.addFragment(fragment,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
}
THE PROBLEM:
Every fragment i add gets recreated "automatically" when configuration change occurs, so its constructor gets called everytime. onCreateView gets called on these instances and not on the ones recreated by my code.
I need onCreateView to get called on fragments instantiated in my tabs in order to set their listviews.
Fragments created "automatically" get replaced by new fragment instances through my onRestoreInstanceState method, but no onCreateView method gets called on these new instances.
I think i'm doing it wrong and maybe there is a better way to save tabs and fragments throught configuration changes... Any help is appreciated.
I once created something similar, which in my case looked like this:
public class DisplayPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "DisplayPagerAdapter";
SparseArray<DisplayFragment> registeredFragments = new SparseArray<DisplayFragment>();
private final Context context;
private final DisplayCoreModule display;
private final FragmentManager fm;
private boolean isAddOrRemoving;
public DisplayPagerAdapter(Context context, FragmentManager fm,
DisplayCoreModule display) {
super(fm);
this.context = context;
this.display = display;
this.fm = fm;
Log.d(TAG, "pages " + display.getPagesCount());
}
public void notifySizeChangingDataSetChange() {
isAddOrRemoving = true;
notifyDataSetChanged();
isAddOrRemoving = false;
}
#Override
public int getCount() {
int count = (display != null && display.getPagesCount() > 0) ? display
.getPagesCount() : 1;
return count;
}
#Override
public int getItemPosition(Object object) {
DisplayFragment frag = (DisplayFragment) object;
if (!display.containsPageId(frag.getPageId())) {
// this will update the 'no information' page with id -1
return POSITION_NONE;
}
if (isAddOrRemoving) {
// recreate all for simplicity
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem " + position);
return DisplayFragment.newInstance(position);
}
#Override
public CharSequence getPageTitle(int position) {
if (display != null && display.getPagesCount() > 0) {
return context.getString(R.string.page) + " " + (position + 1);
} else {
return super.getPageTitle(position);
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem " + position);
DisplayFragment fragment = (DisplayFragment) super.instantiateItem(
container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem " + position);
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
public SparseArray<DisplayFragment> getRegisteredFragments() {
return registeredFragments;
}
}
It has been a long time since I wrote this, so are not able to go into great detail.
However, the key thing to note (I would think), is that I have an external 'bookkeeper' (the DisplayCoreModule). Whenever the amount of fragments would change in DisplayCoreModule, I simply invoked notifySizeChangingDataSetChange() to make the FragmentStatePagerAdapter refresh content.
Regaring some of your problems, FragmentStatePagerAdapter will not invoke onCreateView of your fragments until they are displayed. Usually this means only three fragments: the one you currently look at and one from either side of that fragment.
Perhaps, come to think of it, the root of your problem is missing getItemPosition() with POSITION_NONE. Seem to recall that it was neccesarry for dynamic content to be updated. To make notifyDataSetChanged have a proper effect.
HACKY SOLUTION:
I simply modified my addTab method:
public void addTab(My_Fragment fragment, String title){
boolean createFragment=true;
if (getTabCount()>0){
//all tabs must have different names
for (int i=0;i<getTabCount();i++){
if (getTabTitle(i).equals(title)==true){
createFragment=false;
break;
}
}
}
if (createFragment && fragment!=null){
//this ensures that if a fragment of the same type already exists, that should be the one to be added, tabs are made of fragments of different classes, so there will be no problem while checking this.
for (Fragment f: my_activity.getSupportFragmentManager().getFragments()){
if (fragment.getClass().isInstance(f)){
my_viewPagerAdapter.addFragment((My_Fragment)f,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
return;
}
}
my_viewPagerAdapter.addFragment(fragment,title);
tabLayout.addTab(tabLayout.newTab().setText(title));
}
}
I'm developing an application in which I have to use viewpager and after all items in viewpager is finished I have to call an activity. I'm not able to get event listener for this. Here is what I have been refering too:
https://github.com/chiuki/android-swipe-image-viewer/blob/master/src/com/sqisland/android/swipe_image_viewer/MainActivity.java
Here is what I have done so far:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
ImagePagerAdapter adapter = new ImagePagerAdapter();
viewPager.setAdapter(adapter);
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) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
Log.i("Doing something here", "On Scroll state changed");
}
};
viewPager.setOnPageChangeListener(mListener);
}
private class ImagePagerAdapter extends PagerAdapter {
private int[] mImages = new int[] { R.drawable.libin1,
R.drawable.libin2 };
#Override
public int getCount() {
return mImages.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((ImageView) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Context context = MainActivity.this;
ImageView imageView = new ImageView(context);
int padding = context.getResources().getDimensionPixelSize(
R.dimen.padding_medium);
imageView.setPadding(padding, padding, padding, padding);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setImageResource(mImages[position]);
((ViewPager) container).addView(imageView, 0);
return imageView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((ImageView) object);
}
}
}
My question is how to get event listener if all the items in viewpager is finished.
private OnPageChangeListener mListener = new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
selectedIndex = arg0;
}
boolean callHappened;
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
if( mPageEnd && arg0 == selectedIndex && !callHappened)
{
Log.d(getClass().getName(), "Okay");
mPageEnd = false;//To avoid multiple calls.
callHappened = true;
}else
{
mPageEnd = false;
}
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
if(selectedIndex == adapter.getCount() - 1)
{
mPageEnd = true;
}
}
};
ViewPager.setOnPageChangeListener(mListener);
onPageScrolled or onPageSelected any of these you can use here and also check the selected page is equals to the number of items in the ViewPager.
these three callbacks work like this:
onPageScrollStateChanged is called with state ViewPager.SCROLL_STATE_DRAGGING
onPageScrolled will be called many times, its parameter positionOffset and positionOffsetPixels are keep increasing
onPageScrolled's parameter positionOffset more than 0.5, onPageScrollStateChanged is called with state SCROLL_STATE_SETTLING
onPageSelected is called with next page index as position
onPageScrolled will be called many times, its parameter positionOffset and positionOffsetPixels are keep increasing
onPageScrolled's parameter positionOffset is 1, onPageScrollStateChanged is called with state SCROLL_STATE_IDLE
The logic should not put in onPageSelected and onPageScrollStateChanged, because with them you only know state is changed. only in onPageScrolled you can get the direction. So my implementation is like this:
private int selectedPageIndex = -1;
private boolean exitWhenScrollNextPage = false;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (exitWhenScrollNextPage && position == PAGE_COUNT - 1) {
exitWhenScrollNextPage = false; // avoid call more times
AndroidLog.error("-------- YEAH!");
}
}
#Override
public void onPageSelected(int position) {
selectedPageIndex = position;
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
exitWhenScrollNextPage = selectedPageIndex == PAGE_COUNT - 1;
}
}
Thanks Triode for great answer.
I used the code you posted for my project. In my case, I am finishing the activity if a user swipes to the right on last fragment. It worked fine, but it was still finishing the activity when you swipe to left instead of swiping right.
I made a change in your code and put adapter.getCount() - 1 in place of selectedIndex. In this case it will not finish the activity if user swipes to left.
if( mPageEnd && arg0 == adapter.getCount()-1 && !callHappened){
Log.d(getClass().getName(), "Okay");
mPageEnd = false;//To avoid multiple calls.
callHappened = true;
}else{
mPageEnd = false;
}
Suggested answers are complex than I thought.So I created a simple one.
I used OnPageChangeListener in ViewPager class. Here we have 3 methods.
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
void onPageSelected(int position);
void onPageScrollStateChanged(int state);
It is important to know what are the states in onPageScrollStateChanged() and their sequence of execution.There are 3 states as SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING and SCROLL_STATE_SETTLING.
Here is the execution order.
Scroll_state_dragging --> Scroll_state_settling --> onPageSelected() --> Scroll_state_idle
The idea is keeping a flag inside onPageSelected() to record current page number. Then if user in the last page and swipe left scroll_state_dragging called and launch the next view.
private int pagePosition; // keep a class variable
private int[] layouts= new int[]{
R.layout.welcome_slide1,
R.layout.welcome_slide2,
R.layout.welcome_slide3};
ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
addBottomDots(position);
pagePosition = position;
}
#Override
public void onPageScrolled(int position, float positionOffset, int arg2) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
if (pagePosition == layouts.length - 1) {
launchHomeScreen();
}
}
}
};
private void launchHomeScreen() {
startActivity(new Intent(IntroductionActivity.this, MainDockerActivity.class));
finish();
}
I have created a complete example here.
#Override
public void onPageScrolled(int pageScrolledOn, float positionOffset, int positionOffsetPixels) {
if(positionOffset == 0F){
// meaning you cannot scroll further in current scroll direction
int lastPage = viewPagerAdapter.getCount - 1;
if (pageScrolledOn == lastPage){
//scroll on last page has occured
scrolledOnLastPage = true;
}
}
}
When you are at the last page and you slide to finish the activity the state SCROLL_STATE_SETTLING is skipped and the pager goes directly to SCROLL_STATE_IDLE
private val viewPagerPageChangeListener = object : ViewPager.OnPageChangeListener{
var blnPageTriesToSettle = false
override fun onPageScrollStateChanged(state: Int) {
when(state){
SCROLL_STATE_IDLE ->{
if (!blnPageTriesToSettle){
println("You are at the last page and you slide to finish")
finish()
}
}
SCROLL_STATE_DRAGGING ->{ blnPageTriesToSettle = false }
SCROLL_STATE_SETTLING ->{ blnPageTriesToSettle = true }
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
}
}