All I want to do is a horizontal carousel in Android.
If I have 3 screens A B and C then I want my ViewPager to allow me to move like
A <-> B,
B <-> C,
C <-> A.
GTalk for Android's conversation can be switched like this.
Samsung's homescreen and application screen can be switched like this.
A B and C are fragments and I'm using an adapter that extends FragmentPagerAdapter. All the fragments will contain a webview.
I have looked here here and here but none of them seem to be doing what I want.
Can anyone guide me in the right direction?
(Cross-posting my answer from an identical StackOverflow question)
One possibility is setting up the screens like this:
C' A B C A'
C' looks just like C, but when you scroll to there, it switches you to the real C.
A' looks just like A, but when you scroll to there, it switches you to the real A.
I would do this by implementing onPageScrollStateChanged like so:
#Override
public void onPageScrollStateChanged (int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
int curr = viewPager.getCurrentItem();
int lastReal = viewPager.getAdapter().getCount() - 2;
if (curr == 0) {
viewPager.setCurrentItem(lastReal, false);
} else if (curr > lastReal) {
viewPager.setCurrentItem(1, false);
}
}
}
Note that this calls the alternate form of setCurrentItem and passes false to cause the jump to happen instantly rather than as a smooth scroll.
There are two main drawbacks I see to this. Firstly, upon reaching either end the user has to let the scrolling settle before they can go further. Secondly, it means having a second copy of all of the views in your first and last page. Depending on how resource-heavy your screens are, that may rule out this technique as a possible solution.
Note also that since the view pager doesn't let clicks go through to underlying controls until after the scrolling has settled, it's probably fine to not set up clicklisteners and the like for the A' and C' fragments.
Edit: Having now implemented this myself, there's another pretty major drawback. When it switches from A' to A or C' to C, the screen flickers for a moment, at least on my current test device.
ViewPager settings:
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mViewPager.setAdapter(new YourPagerAdapter(getSupportFragmentManager()));
//Set the number of pages that should be retained to either side of the current page.
mViewPager.setOffscreenPageLimit(1);
mViewPager.setCurrentItem(50);
FragmentPagerAdapter:
public class YourPagerAdapter extends FragmentPagerAdapter {
final int PAGE_COUNT = 100;
final int REAL_PAGE_COUNT = 3;
public YourPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
while (position > REAL_PAGE_COUNT - 1) {
position = position - REAL_PAGE_COUNT ;
}
switch (position) {
case 0:
return FirstFragment.newInstance(position);
case 1:
return SecondFragment.newInstance(position);
case 2:
return ThirdFragment.newInstance(position);
}
return null;
}
#Override
public int getCount() {
return PAGE_COUNT;
}
}
Implement the getItem(int position) like this:
public Fragment getItem(int position)
{
switch(position)
{
case 0:
Fragment A = new A();
return A;
case 1:
Fragment B = new B();
return B;
same for C....
}
}
you can also have a look at here: SimpleViewPager.. download the source and understand it. Hope it helps.
Related
I'm using a ViewPager and displaying a lot of different Fragments inside it, not only in content but they use different classes as well. The list to be displayed should be changed dynamically and even though I manage to swap items around and add new ones to the adapter(and calling notifyDataSetChanged), if I try changing the next item it will still slide to it when using mPager.setCurrentItem(mPager.getCurrentItem() + 1);
I am just adding a new Fragment between the current item and the current next one, it is displayed correctly in the adapter but as the next one was already preloaded then getItem in the adapter is not even called.
Is there another method "stronger" than notifyDataSetChanged that tells my ViewPager that it should get the next item again?
CODE SAMPLES:
The add and get item methods inside my FragmentPagerAdapter(only samples, not the actual code)
public void add(#NonNull Integer fragmentIndex) {
mFragmentOrder.add(fragmentIndex);
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
int selectedFragment = mFragmentOrder(position);
Fragment fragment;
switch (selectedFragment) {
case 1:
fragment = new FragmentA();
break;
case 2:
fragment = new FragmentB();
break;
case 3:
fragment = new FragmentC();
break;
default:
fragment = new FragmentD();
break;
}
return fragment;
}
This is the function used to go to the next item(I don't allow swiping)
public void goToNext() {
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
}
EDITS:
Edit 1: I had already tried using a FragmentStatePagerAdapter instead and setting the OffscreenPageLimit to 0, but to no avail.
Edit 2: [Solution] Using a FragmentStatePagerAdapter AND overwriting the getItemPosition function to return POSITION_NONE or the index in the appropriate cases solved the problem. For some reason even after implementing the right version of this function the normal FragmentPagerAdapter kept delivering the wrong Fragment.
By default, FragmentPagerAdapter assumes that the number and positions of its items remain fixed. Therefore, if you want to introduce for dynamism, you have to provide for it yourself by implementing the getItemPosition(Object object) method in the inherited adapter class. A very basic (but unefficient) implementation would be this:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Every time the parent view is determining whether the position of one of its child views (items) has changed, this code will force the fragment to be recreated. If you want to avoid the recreation when unnecessary, you have to include some logic in the method. Something like this:
#Override
public int getItemPosition (Object object) {
if (fragmentOrder.indexOf(object) == -1) {
return POSITION_NONE;
} else {
return index;
}
}
Finally, pay attention to possible memory leaks by adding an onDestroyView method to your fragments and nullifying the views you are using.
Here is a good discussion of these issues with the two PagerAdapters.
Okay i'll try and make this as clear as possible. I have a Fragment called CheckerManager which contains a ViewPager. This ViewPager will allow the user to swipe between 3 Fragments all of which are an instance of another Fragment called CheckerFragment. I'm using a FragmentPagerAdapter to handle paging. Here's how it looks
private class PagerAdapter extends FragmentPagerAdapter {
CharSequence mTabTitles[];
public PagerAdapter(FragmentManager fm, CharSequence tabTitles[]) {
super(fm);
mTabTitles = tabTitles;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_LOTTO);
case 1:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_DAILY);
case 2:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_EURO);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return mTabTitles[position];
}
#Override
public int getCount() {
return 3;
}
}
I know that the ViewPager will always create the Fragment either side of the current Fragment. So say my 3 CheckerFragments are called A, B and C and the current Fragment is A. B has already been created. But my problem is that even though I am still looking at Fragment A, Fragment B is the 'active' Fragment. Every input I make is actually corresponding to Fragment B and not A. The active Fragment is always the one which has been created last by the ViewPager.
I've looked at quite a few things to see if anyone has had the same problem but i'm finding it difficult to even describe what's wrong. I think it's something to with the fact that all of the ViewPagers fragments are of the same type ie - CheckerFragment. I have a working implementation of a ViewPager inside a fragment elsewhere in the application and the only difference I can tell is that each page is a different type of Fragment.
Any help would be appreciated!
*EDIT
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager(), tabTitles);
ViewPager viewPager = (ViewPager)view.findViewById(R.id.viewPagerChecker);
viewPager.setAdapter(adapter);
I feel pretty stupid but I found out what the issue was. In my CheckerFragment I would call getArguments() to retrieve a String extra and I would use this to determine how to layout the fragment. Problem was I made this extra a static member of CheckerFragment. So every time a new Fragment was created it was using the most recent extra.
Moral of the story - Don't make your fragments extra a static member if you plan on making multiple instances of that fragment.
Been stuck on this for a while.
I currently have 3 fragments each containing lists - A | B | C (Favorites)
A and B retrieve data from online, C is an offline favorite list.
When a user favorites something in A it shows up in the favorite list straight away as the ViewPageAdapter loads 1 extra page off the screen. So A | B are already loaded, which means when I go to favorites (C) it has to reload.
My problem is - When I favorite something in B, the app refuses to reload favorites (C) as C was already loaded when I clicked on B and the only way to see what I have added is refresh the app.
I have tried:
Changing setOffscreenPageLimit(); to 0 so it has to reload each fragment every time - even if clunky just to see it working, and it still refuses to.
NotifyDataSetChanged also hasn't worked or I don't understand it properly.
InstatiateItem in the ViewPageAdapter, but couldn't get that working, couldn't find a good example of it to understand
Creating a new listadapter in the favorite code just to try to get it load the new data - which I can see in the logs it is adding to the favourite list but it just isn't being reloaded by the ViewPageAdapter
Lots of Googling
What happens is, regardless of what I do, when the user goes from B to C, no new code runs as C's code all ran once I clicked on B and it just goes straight to C's list.
I'm using Google's SlidingTabLayout and SlidingTabStrip for the fragments which all works fine, nothing changed in it. found here - https://developer.android.com/samples/SlidingTabsBasic/src/com.example.android.common/view/SlidingTabLayout.html
Code:
public void favourite()
{
//if the user wants to favourite
ParseQuery<ParseObject> query = ParseQuery.getQuery("Favourite");
//queries the id from the local data store in-case they have already favourited
query.whereEqualTo("id",id);
query.fromLocalDatastore();
query.getFirstInBackground(new GetCallback<ParseObject>() {
public void done(ParseObject object, ParseException e)
{
if(object!=null)
{
Toast.makeText(getApplicationContext(),"You have already pinned this",Toast.LENGTH_SHORT).show();
}
else
{
//otherwise create a new ParseObject and save the id,title and backdrop to
//the local datastore
final ParseObject favouriteShow = new ParseObject("FavouriteShow");
favouriteShow.put("id", id);
favouriteShow.put("title", title);
favouriteShow.put("backdrop", backdropPath);
favouriteShow.pinInBackground();
Toast.makeText(getApplicationContext(),"Pinned - "+ title,Toast.LENGTH_SHORT).show();
//need to set favourite as null for it to redraw with the favourited icon
mFavourite.setImageDrawable(null);
mFavourite.setImageResource(R.drawable.favourited_already);
}
}
});
}
ViewPagerAdapter:
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
CharSequence Titles[];
int tabNumber;
public ViewPagerAdapter(FragmentManager fm, CharSequence[] mTitles, int mTabNumber) {
super(fm);
this.Titles = mTitles;
this.tabNumber = mTabNumber;
}
#Override
public Fragment getItem(final int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FragmentA();
break;
case 1:
fragment = new FragmentB();
break;
case 2:
fragment = new FavouritesFragmentC();
break;
}
return fragment;
}
#Override
public CharSequence getPageTitle(int position) {
return Titles[position];
}
#Override
public int getCount() {
return tabNumber;
}
}
Thanks very much for your help.
Adam
Use ViewPager.OnPageChangeListener and update the corresponding
fragment in onPageSelected(position) method using callback pattern.
I've personally had a similar problem.
The solution to this problem depends on the whether the data shown in each tab is contextually related.
If not, the best way to fix this is actually changing the navigational structure of the application. So instead of having tabs, you should use a navigation drawer. Therefore every time the user enters the screen through the drawer, the fragment is created anew. By doing this it will solve the problem you have and improve the user experience.
If so, you may need to create as base fragment for all of the tabs to inherit, with a function called refresh, then you can ask the Adapter to refresh all your tabs. This method is quite hacky mostly because a favourites section doesn't belong in tabs.
I have a ViewPager with 3 Fragments and my FragmentPagerAdapter:
private class test_pager extends FragmentPagerAdapter {
public test_pager(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return fragments[i];
}
#Override
public long getItemId(int position) {
if (position == 1) {
long res = fragments[position].hashCode()+fragment1_state.hashCode();
Log.d(TAG, "getItemId for position 1: "+res);
return res;
} else
return fragments[position].hashCode();
}
#Override
public int getCount() {
return fragments[2] == null ? 2 : 3;
}
#Override
public int getItemPosition(Object object) {
Fragment fragment = (Fragment) object;
for (int i=0; i<3; i++)
if (fragment.equals(fragments[i])){
if (i==1) {
return 1; // not sure if that makes a difference
}
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
}
In one of the page (#1), I keep changing the fragment to be displayed. The way I remove the old fragment is like this:
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().remove(old_fragment1).commit();
And then just changing the value of fragments[1]
I found that I cannot really add or replace the new one or it will complain the ViewPager is trying to add it too with another tag... (am I doing something wrong here?)
All the fragments I display have setRetainInstance(true); in their onCreate function.
My problem is that this usually works well for the first few replacement, but then when I try to reuse a fragment, sometimes (I have not really figured out the pattern, the same fragment may be displayed several times before this happens) it will only show a blank page.
Here is what I have found happened in the callback functions of my Fragment I am trying to display when the problem happens:
onAttach is called (but at that time, getView is still null)
onCreateView is not called (that's expected)
onViewStateRestored is not called (why not?)
onResume is not called (I really thought it would...)
If it changes anything, I am using the support package, my activity is a SherlockFragmentActivity
EDIT (to answer Marco's comment):
The fragments are instantiated in the onCreate function of the Activity, I fill an ArrayList with those fragments:
char_tests = new ArrayList<Fragment>(Arrays.asList(
new FragmentOptionA(), new FragmentOptionB(), new FragmentOptionC()));
The I pick from that list to set fragments[1] (that's all done in the UI thread)
I fixed this by changing test_pager to extends FragmentStatePagerAdapter instead.
I am still confused as to what PagerAdapter should be used depending on the usage. The only thing I can find in the documentation says that FragmentPagerAdapter is better for smaller number of pages that would be kept in memory and FragmentPagerStateAdapter better for a larger number of pages where they would be destroyed and save memory...
When trying to do (fancy?) things with Fragments, I found FragmentStatePagerAdapter is better when pages are removed and re-inserted like in this case. And FragmentPagerAdapter is better when pages move position (see bug 37990)
EDIT: See my answer below-->
I am wanting to have a view that when swiped to the right, the listView is shown. Very much similar to what is implemented in the new Google Play Store (Sample image below). I think its a ViewPager but I tried duplicating it without prevail. I was thinking it may just be that the 'listView Page' width attribute was set to a specific dp but that doesn't work. I also tried modifying pakerfeldt's viewFlow and cant figure out how Google does this
Am I on the right track? If someone has an idea how to duplicate this, I would greatly appreciate it. I think this may become a popular new way of showing a navigation view on tablets....? Code would be best of help. Thank you!!
Swipe right:
Finnished swipe; the layout shows the list and PART OF THE SECOND FRAGMENT (EXACTLY AS SHOWN) The list fragment does not fill the screen:
When the user swipes left, the main page is only shown and if the user swipes left again the viewPager continues to the next page.
The following code achieves the desired effect:
In PageAdapter :
#Override
public float getPageWidth(int position) {
if (position == 0) {
return(0.5f);
} else {
return (1.0f);
}
Reading your question one last time... make sure you also set up specific layouts for each size device. In your screenshots it looks like your trying to run this on a tablet. Are you getting the same results on a phone?
Setting up your Layout
Make sure your layout is simular to this and has the ViewPager:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/body_background">
<include layout="#layout/pagerbar" />
<include layout="#layout/colorstrip" />
<android.support.v4.view.ViewPager
android:id="#+id/example_pager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
</LinearLayout>
Setting up your Activity
Setup your PagerAdapter in your "FragmentActivity" and make sure you implement "OnPageChangeListener". Then properly setup your PagerAdapter in your onCreate.
public class Activity extends FragmentActivity
implements ViewPager.OnPageChangeListener {
...
public void onCreate(Bundle savedInstanceState) {
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.example_pager);
pager.setAdapter(adapter);
pager.setOnPageChangeListener(this);
pager.setCurrentItem(MyFragment.PAGE_LEFT);
...
}
/* setup your PagerAdapter which extends FragmentPagerAdapter */
static class PagerAdapter extends FragmentPagerAdapter {
public static final int NUM_PAGES = 2;
private MyFragment[] mFragments = new MyFragment[NUM_PAGES];
public PagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public int getCount() {
return NUM_PAGES;
}
#Override
public Fragment getItem(int position) {
if (mFragments[position] == null) {
/* this calls the newInstance from when you setup the ListFragment */
mFragments[position] = MyFragment.newInstance(position);
}
return mFragments[position];
}
}
...
Setting up your Fragment
When you setup your actual ListFragment (your listViews) you can create multiple instances with arguments like the following:
public static final int PAGE_LEFT = 0;
public static final int PAGE_RIGHT = 1;
static MyFragment newInstance(int num) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("num", num);
fragment.setArguments(args);
return fragment;
}
When you reload the listViews (how ever you decide to implement this) you can figure out which fragment instance you are on using the arguments like so:
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
If you step through your code you will notice that it will step through each instance so the above code is only needed in your onCreate and a reload method may look like this:
private void reloadFromArguments() {
/* mNum is a variable which was created for each instance in the onCreate */
switch (mNum) {
case PAGE_LEFT:
/* maybe a query here would fit your needs? */
break;
case PAGE_RIGHT:
/* maybe a query here would fit your needs? */
break;
}
}
Few Sources that may help you out with examples that you could build from rather then starting from scratch:
More explanation and example from playground.
http://blog.peterkuterna.net/2011/09/viewpager-meets-swipey-tabs.html
which is references to:
http://code.google.com/p/android-playground/
More info and some good linkage.
http://www.pushing-pixels.org/2012/03/16/responsive-mobile-design-on-android-from-view-pager-to-action-bar-tabs.html
If you have more specific questions post and I can always Edit (update) my answer to address your questions. Good Luck! :)
Sorry for the late update. I implemented this from walkingice on Gethub with very little modification. Just use a conditional statement for a GestureDetector to swipe it into view only when a ViewPager id of '0' is in view. I also added a toggle whithin my ActionBar
ViewPager is a part of the Compatibly Package
If you're using Fragments, then you can use ViewPager to swipe between them.
Here's an example of combining Fragments and ViewPager
In your particular case, you would want to create a ListFragment and then implement ViewPager.
I think you are looking to implement a "side navigation" beside a standard ViewPager.
I've read 2 different articles on this pattern:
The first one on the pattern itself:
Android Ui Pattern Emerging UI Pattern - Side Navigation
The second on a more detailed way of who to build it:
Cyril Mottier Fly-in app menu #1 #2 #3
This second article is referenced in Android Ui Pattern blog.
With a little Trick, the behavior can be achieved with the ScrollView-Behavior inside the ViewPager. If you only want to restrict the area of the most left fragment, you can restrict the scroll limits of the ScrollView.
In your case:
in the onPageChangeListener of the ViewPager do something like that:
#Override
public void onPageScrollStateChanged(int arg0) {
restrictLeftScroll();
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
restrictLeftScroll();
}
#Override
public void onPageSelected(int arg0) {
}
private void restrictLeftScroll() {
if (display != null) {
/* get display size */
Point size = new Point();
display.getSize(size);
/* get desired Width of left fragment */
int fragmentWidth = getResources().getDimensionPixelSize(R.dimen.category_fragment_width);
if (mViewPager.getScrollX() < size.x - fragmentWidth) {
mViewPager.scrollTo(size.x - fragmentWidth, mViewPager.getScrollY());
}
}
}
This piece of code worked for me without problems. ;)