I'm working on a specialized eBook application, where one of the requirements is that it should only display one page of text at a time, with formatting and what not. This proved more challenging than I thought it would, but I've finally managed to do it successfully.
However, now I've run into another problem, namely that the client wants to be able to flip back and forth between pages, ViewPager-style, where the page follows the finger. Now, the number of pages in these books and scriptures aren't predetermined, as I calculate them on-the-fly, each time a chapter is loaded, so that the view automatically handles changes in text size, etc.
I've been looking into various forms of "unlimited ViewPager-flipping", but thus far I haven't been able to find one that seems to work for my purpose.
What I've been considering, and that I hope someone may be able to help me with, is this:
Three pages, sort of. The one being viewed currently, and one on each side for the flipping.
When the user flips to a new page, the views will switch places behind the scenes, so that the new page becomes the one in the middle, and loads the next page, while moving the previously viewed page to the opposite end.
It doesn't technically have to be a ViewPager, if the animation, including the gesture-following, can be achieved in some other way.
I would offer up extra reputation points for this, as I'd really love to solve this quickly, but unfortunately I haven't managed to get a lot of those yet.
When you want to solve your problem with a ViewPager, you need this in your onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPager = (ViewPager) findViewById(R.id.pager);
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
// Capture ViewPager page swipes
ViewPager.SimpleOnPageChangeListener ViewPagerListener = new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (fistswitch) {
mPager.setCurrentItem(1);
fistswitch = false;
} else {
// here you can use the replace method, to switch the
// fragment behind the scenes
ArrayList<Fragment> fragmentList = new ArrayList<Fragment>();
Collections.addAll(fragmentList, new Fr2(), new Fr3(), new Fr4());
mPagerAdapter.addFragments(fragmentList);
// after the first switch you can use the the second page
// continuously as middle
mPager.setCurrentItem(1);
}
}
};
mPager.setOnPageChangeListener(ViewPagerListener);
mPagerAdapter = new ViewPagerAdapter(fm);
mPager.setAdapter(mPagerAdapter);
ArrayList<Fragment> fragmentList = new ArrayList<Fragment>();
Collections.addAll(fragmentList, new Fr1(), new Fr2(), new Fr3());
mPagerAdapter.addFragments(fragmentList);
}
The pager adapter class can be a private class in your activity like this:
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<Fragment>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<Fragment> fragments) {
fragmentList.clear();
fragmentList.addAll(fragments);
notifyDataSetChanged();
}
public void replaceFragment(int position, Fragment fr) {
fragmentList.remove(position);
fragmentList.add(position, fr);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
#Override
public int getCount() {
return fragmentList.size();
}
}
I have not tested the code, but it works for me in a similar case.
There are some good ways to add animation to a ViewPager and alternatively use your own custom views. The first is an open source project called JazzyViewPager by jfeinstein10
This library is really nice and have some animations built in. I've looked at the source in the past and it should be simple to create your own animations if necessary.
The second option is using what's used in JazzyViewPager, namely ViewHelper, which is a class is in the NineOldAndroids animation library by Jake Wharton.
Related
There is 3 TabLayout with ViewPager in my application.
If I swipe for change Tabs then it behaves strangely.
Below is the scenario.
Let's say 3 Tabs A, B, C.
Default Tab A will open when I start App.
Go to B From A - Works OK.
Go to A From B - Works Ok.
Go To B From A - Works Ok.
Go To C From B - Works Ok.
Now Problem Starts here.
Go To B From C - It will Load A and B Both but display Only B.
Why A and B loads if I come from C ??
Below is my code.
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
ViewPagerAdapter adapter;
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new FragmentOpen(), "OPEN");
adapter.addFragment(new FragmentClose(), "CLOSE");
adapter.addFragment(new FragmentTest(), "TEST");
viewPager.setAdapter(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);
}
}
I want to load Tabs every time.
For that, I use below code.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(adapter != null){
adapter.getItem(position).onResume();
}
Toast.makeText(getApplicationContext(), "Page Selected", Toast.LENGTH_LONG).show();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
But it will always load both tabs when I came from C.
What I want is: only Tab B must load when came from C, not Both.
Its not possible when using ViewPager. ViewPager loads at least one right/left adjacent fragment of current fragment as per your swipe direction.
If you read the ViewPager documentation, there is a method ViewPager.setOffscreenPageLimit(LIMIT) which supports off screen page limit and its default value is 1.
SetOffscreenPageLimit:
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
See documentation.
Here are some related answers:
1. ViewPager.setOffscreenPageLimit(0) doesn't work as expected
2. How can make my ViewPager load only one page at a time ie setOffscreenPageLimit(0);
3. How does setOffscreenPageLimit() improve ViewPager performance by retaining more offscreen Fragments?
its Default behavior of ViewPager if you are in A then it loads B
also and when your reach B it will Load C and A also is Loaded now
when to reach at C its destroy the View of A and now when you come
again to B it will Load A again, in a simple way ViewPager Maintain
the One Next and One previous state of View by Default.
to Overcome this you should use ViewPager.setOffscreenPageLimit(number) here number is the count of page to load Simulationally for Next and Previous View.
I had only 3 tabs and settingViewPager.setOffscreenPageLimit(2) worked for me.
Try ViewPager.setOffscreenPageLimit(totTabs - 1)
Since in Android L the the Action bar navigation modes are deprecated I'm searching an other way to have the tab and I found that is possible to use the PagerTabStrip (android.support.v4.view.PagerTabStrip), so I created a FragmentPageAdapter in this way:
public class TitleAdapter extends FragmentPagerAdapter {
private final String titles[] = new String[] { "Home", "Events", "People", "Books"};
private final Fragment frags[] = new Fragment[titles.length];
Context context;
public TitleAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
Log.v("TitleAdapter - getPageTitle=", titles[position]);
return titles[position];
}
#Override
public Fragment getItem(int position) {
Log.v("TitleAdapter - getItem=", String.valueOf(position));
//return frags[position];
switch (position) {
case 0:
return Home.newInstance(0, "Home");
case 1:
return Events.newInstance(1, "Events");
case 2:
return People.newInstance(2, "People");
case 4:
return Books.newInstance(3, "Books");
}
return null;
}
#Override
public int getCount() {
return frags.length;
}
}
The strange way that i see in LogCat is that the method getItem() is called 4 times when the mainActivity starts so I've to wait a lots because in each Tab there is a quite long list and this list is populated via HTTP request calling a web service.
I wish load only a fragment each times and not all. When i used actionbar.Tablistener it was possible but now the method is deprecated so is there a way to do that?
I set the adapter and the viewPager in the onCreate method in this way:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.prova_page_tab_stripes);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
titleAdapter = new TitleAdapter(getSupportFragmentManager());
mViewPager.setAdapter(titleAdapter);
mViewPager.setOffscreenPageLimit(1);
}
The number of pages initialized depends on setOffscreenPageLimit function of ViewPager. As per android doc in http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int),
public void setOffscreenPageLimit (int limit)
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. This setting defaults to 1.
Parameters
limit How many pages will be kept offscreen in an idle state.
Set it to 1 or keep it to default if you wan to limit pages to be retained.
But if you want to load the data only in one page at a time, you can determine when fragment becomes visible and then load the data (insted of loading in onCreateView.)
Refer this question: How to determine when Fragment becomes visible in ViewPager
I understand the lowest number I can give setOffscreenPageLimit(int) is 1. but I need to load one page at a time because memory problems.
Am i going to have to use the old style tabhost etc? or is there a way/hack I can make my viewPager load one page at a time?
My Adapter extends BaseAdapter with the ViewHolder patern.
I was having the same problem and I found the solution for it:
Steps:
1) First Download the CustomViewPager Class from this link.
2) Use that class as mentioned below:
In Java:
CustomViewPager mViewPager;
mViewPager = (CustomViewPager) findViewById(R.id.swipePager);
mViewPager.setOffscreenPageLimit(0);
In XML:
<com.yourpackagename.CustomViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipePager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Now only one page will be loaded at once.
P.S: As per the question's requirement, I have posted the solution for Viewpager. I haven't tried the same with TabLayout yet. If I will find any solution for that I will update the answer.
In this file, KeyEventCompat is used it may not found by the android studio because KeyEnentCompat class was deprecated in API level 26.0.0 so you need to replace KeyEventCompat to event for more details you can view
https://developer.android.com/sdk/support_api_diff/26.0.0-alpha1/changes/android.support.v4.view.KeyEventCompat
As far as I know, that is not possible when using the ViewPager. At least not, when you want your pages to be swipe-able.
The explaination therefore is very simple:
When you swipe between two pages, there is a Point when both pages need to be visible, since you cannot swipe between two things when one of those does not even exist at that point.
See this question for more: ViewPager.setOffscreenPageLimit(0) doesn't work as expected
CommonsWare provided a good explaination in the comments of his answer.
but I need to load one page at a time because memory problems.
That presumes that you are getting OutOfMemoryErrors.
Am i going to have to use the old style tabhost etc?
Yes, or FragmentTabHost, or action bar tabs.
or is there a way/hack I can make my viewPager load one page at a time?
No, for the simple reason that ViewPager needs more than one page at a time for the sliding animation. You can see this by using a ViewPager and swiping.
Or, you can work on fixing your perceived memory problems. Assuming this app is the same one that you reported on earlier today, you are only using 7MB of heap space. That will only result in OutOfMemoryErrors if your remaining heap is highly fragmented. There are strategies for memory management (e.g., inBitmap on BitmapOptions for creating bitmaps from external sources) that help address such fragmentation concerns.
My Adapter extends BaseAdapter with the ViewHolder patern.
BaseAdapter is for use with AdapterView, not ViewPager.
I have an Answer for this. The above said method setUserVisibleHint() is deprecated and you can use setMaxLifecycle() method. For loading only the visible fragment you have to set the behaviour to BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT in the viewpager adapter. ie; in the Constructor. And for handling the fragment use onResume() method in the fragment.
In this way you can load only one fragment at a time in the viewpager.
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return ArrayListFragment.newInstance(position);
}
}
In Kotlin:
class MyAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT )
Also use with FragmentPagerAdapter (now deprecated) in same way
By using this method you can load one page at time in tab layout with view pager`
#Override
public void onResume() {
super.onResume();
if (getUserVisibleHint() && !isVisible) {
Log.e("~~onResume: ", "::onLatestResume");
//your code
}
isVisible = true;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isVisible) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//your code
}
}, 500);
}
}
`
Override the setUserVisibleHint and add postDelayed like below in your every fragments.
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
if (isVisibleToUser)
Handler().postDelayed({
if (activity != null) {
// Do you stuff here
}
}, 200)
super.setUserVisibleHint(isVisibleToUser)
}
I can manage by this way and its working fine now for me.
First, copy in the SmartFragmentStatePagerAdapter.java which provides the intelligent caching of registered fragments within our ViewPager. It does so by overriding the instantiateItem() method and caching any created fragments internally. This solves the common problem of needing to access the current item within the ViewPager.
Now, we want to extend from SmartFragmentStatePagerAdapter copied above when declaring our adapter so we can take advantage of the better memory management of the state pager:
public abstract class SmartFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
// Sparse array to keep track of registered fragments in memory
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public SmartFragmentStatePagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
// Register the fragment when the item is instantiated
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
// Unregister when the item is inactive
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
// Returns the fragment for the position (if instantiated)
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
// Extend from SmartFragmentStatePagerAdapter now instead for more dynamic ViewPager items
public static class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
private static int NUM_ITEMS = 3;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
// 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: // Fragment # 0 - This will show FirstFragment
return FirstFragment.newInstance(0, "Page # 1");
case 1: // Fragment # 0 - This will show FirstFragment different title
return FirstFragment.newInstance(1, "Page # 2");
case 2: // Fragment # 1 - This will show SecondFragment
return SecondFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
// Returns the page title for the top indicator
#Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
}
You actually don't need a custom ViewPager.
I had the same issue and I did like this.
Keep the setOffscreenPageLimit() as 1.
Use fragment's onResume and onPause lifecycle methods.
Initialize and free-up memories on these lifecycle methods.
I know this is an old post, but I stumbled upon this issue and found a good fix if your loading fragments. Simply, check if the user is seeing the fragment or not by overriding the setUserVisibleHint(). After that load the data.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
getData(1, getBaseUrl(), getLink());
}
}
I am having problems with fragments or maybe rather the way I want them to work.
In my activity I have a layout container (R.id.container1) to which I add a fragment (FragMain) programmatically at startup.
In the fragment I have 2 Fragments (Frag1, Frag2) and a ViewPager which loads several other fragments (FragA, FragB, FragC, FragD, FragE) via a FragmentPagerAdapter.
On a button press I replace the whole content of R.id.container1, so FragMain is replaced with another Fragment (FragSub).
Now when I press the back button, FragMain is loaded again, but the ViewPager isn't fully initialized, some Fragments are missing. From what I observed, it's always FragB and FragD missing, If I scroll to the empty views, the app crashes. The other fragments seem to be fine. What's going wrong here?
This thread suggests using getChildFragmentManager in creation of the PagerAdapter, but I am already doing that... Fragment in ViewPager not restored after popBackStack
I am accessing the fragments via e.g.
((FragA)((PagerAdapter)viewPager.getAdapter()).getItem(0));
Is this the best way?
Some Code :
MainActivity's onCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pagerFragList = new Vector<Fragment>();
pagerFragList.add(Fragment.instantiate(this, FragA.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragB.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragC.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragD.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragE.class.getName()));
FragMain fragMain = (FragMain)getSupportFragmentManager().findFragmentByTag(FragMain.debugTag);
if(fragMain==null) fragMain = new FragMain();
FragmentHelper.replaceSupportFragment(this, fragMain, R.id.container1, false, FragMain);
}
Helper
public Vector<Fragment> getFragList() {
return pagerFragList;
}
FragMain's onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
pagerAdapter = new PagerAdapter(getChildFragmentManager(), ((MainActivity)getActivity()).getFragList());
viewPager = ((ViewPager)view.findViewById(R.id.viewPager));
viewPager.setAdapter(pagerAdapter);
viewPager.setOffscreenPageLimit(5);
viewPager.setCurrentItem(0, true);
}
The PagerAdapter
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments=fragments;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
Have no idea why this seems to be fixing it on my end, but try commenting out your "viewPager.setOffscreenPageLimit(5);" line.
I'm still investigating and will be back with more if I find anything else useful out.
The other thing it turns out that I had to do was override the "state" methods of a FragmentStatePagerAdapter like this:
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
//do nothing
}
In order to bypass whatever strange thing that was causing problems (must have had something to do with saving and restoring the states of nested fragments in a ViewPager...
There are alot of permutations involving the state of fragments and backstack transitions. For debug, it may help to isolate the event you are working with (backbutton , orientation change ... ) and to look at the following before and after your event....
state of fragment manager which includes the backstack i believe...
//debug
getFragmentManager().dump("", null,
new PrintWriter(System.out, true), null);
see this thread
I've been working on an app to display media that has a layout that resembles the below mockup. Each fragment is a pair of linearlayout elements with 4 custom layouts to show a imageview and textview.
If I have more than 2 fragments and flick right-left to navigate through the various fragments the 4 custom imageviews are not being displayed inside the fragment. I assume it's due to garbage collection resulting from Android loading all images onto the heap.
I've tried using the onResume() methods of each fragment to rebuild the layouts when they are visible, with disastrous results.
Since different users may have different fragment counts based on how they set up their media, what would be considered best practice for an activity that's image heavy like this. Also does anyone have any suggestions for how to tell if the fragment's layout needs to be rebuilt.
This is my first Android app, the learning curve feels quite steep.
EDIT: As requested, here is the adapter and the fragment.
public static class HomeScreenPagerAdapter extends FragmentPagerAdapter {
public HomeScreenPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
MediaPayload mediaWrapper = new MediaPayload();
mediaWrapper.Item = items[i];
Fragment fragment = new EhsFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("InterActivityPayload", payload);
bundle.putSerializable("MediaWrapper", mediaWrapper);
fragment.setArguments(bundle);
return fragment;
}
#Override
public int getCount() {
return items.length;
}
#Override
public CharSequence getPageTitle(int position) {
return items[position].Name;
}
}
The fragment is 500+ lines, so I dropboxed it. I can post it inline if people prefer, but it's a lot of code.