I have a ViewPager/FragmentPagerAdapter combo with a negative page margin so that the pages slightly overlap each other. Problem is, the center page is not on top. The page to the left of it obscures it. I would like it so that the center page is always displayed on top. Is there an elegant way to do this?
The z-order of the pages are determined by the order of their creation, so the latter page will be resting on top of the page created earlier (refer first image below).
In normal case, this shouldn't be any problem as the pages are arrange side-by-side with no overlapping region, so the z-order doesn't really matters. However, in the case of multiple visible page with negative margin(overlapping), the selected page might be blocked partially by the page beside it.
One way to overcome this is to keep a reference of the page during instantiation and use it to change it's z-order with view.bringToFront().
To put this in code:
public class MyActivity extends Activity {
private ViewPager vp = null;
private SparseArray<View> viewCollection = new SparseArray<View>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
vp = (ViewPager)findViewById(R.id.viewpager);
vp.setPageMargin(-20);
vp.setOffscreenPageLimit(3);
vp.setOnPageChangeListener(new ViewPager.OnPageChangeListener(){
#Override
public void onPageSelected(int pos) {
View view = viewCollection.get(pos); // ### Get target page reference
view.bringToFront(); // ### change z-order to the top
}
});
vp.setAdapter(new MyAdapter());
}
public class MyAdapter extends PagerAdapter {
#Override
public Object instantiateItem(ViewGroup container, int position) {
View pageLayout = getLayoutInflater().inflate(R.layout.page_layout, container, false);
final Integer pageId = position;
TextView pageText = (TextView)pageLayout.findViewById(R.id.page_id);
pageText.setText(String.valueOf(position));
pageText.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
vp.setCurrentItem(pageId); // ### select the clicked page
}
});
// paint gray shades onto the page background
pageLayout.setBackgroundColor(Color.argb(255, position*40, position*40, position*40));
viewCollection.put(position, pageLayout); // ### store the reference
container.addView(pageLayout);
return(pageLayout);
}
#Override
public int getCount() {
return 8; // any arbitrary number
}
#Override
public float getPageWidth(int position) {
return(0.33f);
}
#Override
public boolean isViewFromObject(View view, Object obj) {
return (view == obj);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
viewCollection.remove(position); // ### remove the reference
}
}
}
Related
I am currently using Material Design in an Android app that I am making. In this app, I am using the Material Design tab layout to display some information that I am receiving. However when I tap the tabs, the animation is not smooth, and it is very abrupt. Sliding to go to the other tab, however is very smooth.
mTabLayout = (TabLayout) findViewById(R.id.chem_tab_layout);
mGenericAdapter = new GenericPagerAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.view_pager);
mPager.setAdapter(mGenericAdapter);
//Notice how the Tab Layout links with the Pager Adapter
mTabLayout.setTabsFromPagerAdapter(mGenericAdapter);
//Notice how The Tab Layout and View Pager object are linked
mTabLayout.setupWithViewPager(mPager);
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout){
#Override
public void onPageSelected(int position) {
mGenericAdapter.notifyDataSetChanged();
}
});
That is my code for setting the adapter, etc.
This is my custom adapter code for the tabs:
class GenericPagerAdapter extends FragmentStatePagerAdapter {
public GenericPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ChemGridActivity.MyFragment myFragment = new ChemGridActivity.MyFragment();
return myFragment;
}
#Override
public int getCount() {
return 3; //returns number of tabs that need to be created
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) return "Chemistry";
if (position == 1) return "Mathematics";
if (position == 2) return "Physics";
else return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
I feel that the choppy transition between tabs is caused by the overriden method onPageSelected method when I add onPageChangeListener. What do I add to this method to make tapping on tabs a smoother animation?
Without knowing much about the internals of your classes, I imagine the problem is not that you have a listener, but what you are doing inside that listener.
In the case of most adapters notifyDataSetChanged() will cause it to re-render the entire view again (including all pages).
Seeing as you haven't specified what the intent here with the notification is, it's hard to tell you how you can do this in an alternative way, but you do need to do something less intensive if you want the animation to remain smooth.
I suspect you just want to change which fragment is shown, in which case just use the FragmentManager where necessary, remembering to reuse fragments which have already been seen once.
EDIT Based on additional info in comments
#Override
public Fragment getItem(int position) {
//POSITION_SOMETHINHG would be one of a set of constants to indicate hwa to display
return ChemGridActivity.MyFragment.newInstance(ChemGridActivity.MyFragment.POSITION_SOMETHINHG);
}
public class ChemGridActivity.MyFragment ... {
private static final String KEY_DISPLAY_TYPE = "KEY_DISPLAY_TYPE";
public static final int POSITION_SOMETHINHG = 11111;
public static MyFragment newInstance(int display) {
MyFragment f = new MyFragment();
Bundle bund = new Bundle();
bund.putInt(KEY_DISPLAY_TYPE, display);
f.setArguments(bund);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mDisplay = args.getInt(KEY_DISPLAY_TYPE, 0);
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.my_layout, container, false);
//TODO: change something based on mDisplay
return view;
}
I have a viewPager with say 4 pages. All 4 pages uses same Xml. When i do an event in 1st page somehow it always triggers in the last page.
Here is my PagerAdapter
#Override
public Object instantiateItem(ViewGroup container, int pos) {
View desktopView;
OnTouchListener tl = null;
desktopView = act.getLayoutInflater().inflate(
act.getViewPagerLayout(groupName), null);
RelativeLayout rr_appContainer, rr_dialogContainer;
ImageView rr_home_container = (ImageView) desktopView
.findViewById(R.id.imageView_forClick);
Button buttonChange = (Button)desktopView.findViewById(R.id.B1);
Button buttonDelete = (Button)desktopView.findViewById(R.id.B2);
rr_appContainer = (RelativeLayout) desktopView
.findViewById(R.id.rr_home_container);
rr_dialogContainer = (RelativeLayout) desktopView
.findViewById(R.id.rr_dialogView);
..........
buttonDelete.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
deletestuff();
}
buttonChange.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
changeColorOfStuff();
}
.....
return desktopView;
}
What is happening is, When i click on buttonChange from 1st page it supposed to change the color of text on 1st page, but actually it is changing color of the last page. Similarly buttonDelete is deleting color from last page.
Regardless of what page i am in, its reflecting those changes on last page.
Any help would be appreciated.
From the context given here, the deleteStuff() and changeColorOfStuff() can only be members of the Fragment/Activity that owns the adapter, or the adapter itself. So these methods can only act on members of those classes. ViewPager asks the adapter for the fragments it is going to display. However, the text in the fragment being shown by the ViewPager belong to the that fragment. To act on that text, you need a method that's a member of that fragment. The usual way to do this is to use a custom fragment. For example:
Custom Fragment (inner class):
public static class CustomFragment extends Fragment {
//members of the fragment
TextView yourTextView;
...
public static CustomFragment newInstance(int pos) {
CustomFragment fragment = new CustomFragment();
//get whatever info you need for this page
Bundle args = getInfoSomehow(pos);
fragment.setArguments(args)
return fragment;
}
#Override
public View onCreateView(Layout inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(....
yourTextView = root.findViewById(...) //this is the text view you want to change things in
//all the stuff you're currently doing in instantiateItem()
return root;
}
private void deleteStuff() {
//whatever you need to do. But notice that here it's acting on the TextView that belongs to this particular fragment
}
private void changeColorOfStuff() {...}
...
}
Then in your instantiateItem(...)
#Override
public Object instantiateItem(ViewGroup container, int pos) {
return CustomFragment.newInstance(pos);
}
I have a ListView that contains rows of ViewPagers. When a ViewPager is scrolled off screen, I want to save its page position. When the user scrolls to the ViewPager again, I then want to restore its last saved page position.
I'm trying to accomplish this with the following code:
public class MyBaseAdapter extends BaseAdapter {
// Row ids that will come from server-side, uniquely identifies each
// ViewPager
private List<String> mIds = new ArrayList<String>();
private Map<String, Integer> mPagerPositions = new HashMap<String, Integer>();
#Override
public View getView(int position, View convertView, ViewGroup parent) {
String id = mIds.get(position);
View rootView = convertView;
if (rootView == null) {
rootView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.row, null);
}
ViewPager viewPager = (ViewPager) rootView.findViewById(R.id.pager);
Integer pagerPosition = mPagerPositions.get(id);
if (pagerPosition != null) {
viewPager.setCurrentItem(pagerPosition);
}
viewPager.setOnPageChangeListener(new MyPageChangeListener(id));
return rootView;
}
#Override
public int getCount() {
return mIds.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
private class MyPageChangeListener extends
ViewPager.SimpleOnPageChangeListener {
private String mId;
public MyPageChangeListener(String id) {
mId = id;
}
#Override
public void onPageSelected(int position) {
mPagerPositions.put(mId, position);
}
}
}
The page position gets saved on the first invocation of setPagerPosition(). Afterwards, when the ViewPager goes off-screen, onPageSelected() is called with the first page position that was saved — overwriting any previous onPageSelected() calls that occurred when the ViewPager was still visible.
What's the right way to do this?
The solution was to not overwrite the pager position when the ViewPager is not visible. There appears to be a bug in Android where onPageSelected is called when the ViewPager goes off screen when scrolling through the ListView.
#Override
public void onPageSelected(int position) {
if (viewPager.isShown()) {
mTopicPositions.put(mId, position);
}
}
I think you can get the current displayed position by indexOfChild(viewPager.getFocusedChild()) and store it in a shared preference, and can retrieve it whenever required. Hope this helps
In the CustomPagerAdapter of the ViewPager, in instantiateItem() method I'm trying to create an TextView and then for each page set a different text depending on certain condition. Text is read from a pages Cursor. Here is a code:
#Override
public Object instantiateItem(ViewGroup collection, int position) {
sc = new ScrollView(context);
sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
sc.setFillViewport(true);
tv = new TextView(context);
if(position < count) {
tv.setText(pages.getString(1));
pages.moveToPosition(position);
}else {
tv.setText("LOCKED");
}
tv.setTag(TAG_PAGE + position);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(Color.BLACK);
tv.setTextSize(30);
sc.addView(tv);
((ViewPager) collection).addView(sc);
return sc;
}
However ViewPager behaves not as expected. The first and the second page have the same text, rest of the pages has a sign "LOCKED" as expected. When I swipe into the 4th page and come back to the first page then the first page consists of the text that suppose to be in the second page. I also tried to use myViewPager.setOffscreenPageLimit(numberOfPages) however it doesn't help.
I found this answer:
"Inside of instantiateItem, the position parameter is the position that is in need of rendering. It is NOT the position of the currently focused item that the user would see. The pages to the left and right of the currently displayed view need to be pre rendered in memory so that the animations to those screens will be smooth. "
It make sense to me but how then can I correctly display the pages content and then update it if desired? Please advise if there is different way to do it with skipping instantiateItem() method that introduce the mess and confusion into the problem. Thank you.
I have solved this problem by using a different implementation:
// Adapter class
private static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return PageFragment.newInstance(pages[index]); // Pages is an array of Strings
}
#Override
public int getCount() {
return numberOfPages;
}
}
// PageFragment class
public class PageFragment extends Fragment {
TextView tv;
public static PageFragment newInstance(String page) {
PageFragment pageFragment = new PageFragment();
Bundle bundle = new Bundle();
bundle.putString("pageContent", page);
pageFragment.setArguments(bundle);
return pageFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
tv = (TextView) view.findViewById(R.id.text_view);
tv.setText(getArguments().getString("pageContent"));
return view;
}
}
You can Create ViewPager Object and then set Listener onthis object.
ViewPager myPager = (ViewPager) findViewById(R.id.yourPagerid);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
myPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
//You can change textview word according to current page
switch (position) {
case 0:
break;
case 1:
break;
case 2:
break;
}
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// Log.d("check","onPageScrolled");
}
#Override
public void onPageScrollStateChanged(int arg0) {
// Log.d("check","onPageScrollStateChanged");
}
});
I would like to implement a ViewPager which uses Fragments and can be swiped in a curcular motion e.g. Page (A<-->B<-->C<-->A).
I have read a couple of posts on how this is done, e.g. returning a fake count of how many elements there are and setting the position at the start in the middle.
how to create circular viewpager?
These all seem to be based of a PagerAdapter. When I try to do a similar thing while extending FragmentPagerAdapter, as soon as I return a fakeCount of pages I get an exception when I Swipe through my Fragments, I only have 2 Fragments.
Exception: java.lang.IllegalStateException: Can't change tag of fragment.
I think this is caused as the FragmentManager thinks I am in position 2 but position 2 points to the fragment at position 0. Does anyone know how I can avoid this? I am thinking I should experiment with extending Fragmentmanager. Any examples or help with this would be greatly appreciated.
I know it is a bit late but this is how it worked for me:
I needed a circular swipe between 3 fragments, so I made those 3 and two more virtual to help me implement the page looping:
public static class FirstViewFragment extends Fragment {
// Empty Constructor
public FirstViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
public static class SecondViewFragment extends Fragment {
// Empty Constructor
public SecondViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_2, container, false);
}
}
public static class ThirdViewFragment extends Fragment {
// Empty Constructor
public ThirdViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
And two more virtual fragments that enabled me to swipe left from the first and right from the last. The first virtual inflates the same layout as the last actual and the last virtual the same layout as the first actual:
public static class StartVirtualFragment extends Fragment {
public StartVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
public static class EndVirtualFragment extends Fragment {
public EndVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
My Adapter:
private class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return new StartVirtualFragment();
case 1:
if (firstViewFragment == null) {
firstViewFragment = new FirstViewFragment();
}
return firstViewFragment;
case 2:
if (secondViewFragment == null) {
secondViewFragment = new SecondViewFragment();
}
return secondViewFragment;
case 3:
if (thirdViewFragment == null) {
thirdViewFragment = new ThirdViewFragment();
}
return thirdViewFragment;
case 4:
return new EndVirtualFragment();
}
return null;
}
#Override
public int getCount() {
return 5;
}
}
And my page listener I used the onPageScrollStateChanged to set the correct page and implement the loop:
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
int pageCount = viewPager.getChildCount();
int currentItem = viewPager.getCurrentItem();
if (currentItem == 0) {
viewPager.setCurrentItem(pageCount - 2, false);
} else if (currentItem == pageCount - 1) {
viewPager.setCurrentItem(1, false);
}
}
}
});
And in the end:
viewPager.setCurrentItem(1);
Hope I helped
I have a project in the GitHub with some widgets I've created. Here it its:
https://github.com/CyberEagle/AndroidWidgets
In the following package, there are the adapters to be used with the CircularViewPager:
https://github.com/CyberEagle/AndroidWidgets/tree/master/src/main/java/br/com/cybereagle/androidwidgets/adapter
First, you will use CircularViewPager instead of ViewPager in your layout. The CircularViewPager is here: https://github.com/CyberEagle/AndroidWidgets/blob/master/src/main/java/br/com/cybereagle/androidwidgets/view/CircularViewPager.java
This ViewPager expects a WrapperCircularPagerAdapter, instead of a PagerAdapter. This wrapper is used to trick the ViewPager, making it to think there are a lot of items in the ViewPager, but it actually repeat your items to make the circular effect. So, instead of implementing either PagerAdapter, FragmentPagerAdapter or FragmentStatePagerAdapter, you will implement either CircularFragmentPagerAdapter, CircularFragmentStatePagerAdapter or CircularPagerAdapter. Then, you will wrap your adapter with the WrapperCircularPagerAdapter and set the wrapper in the CircularViewPager, instead of your adapter. Also, when it's time to notify dataset changed, you will call the notifyDatasetChanged() in the wrapper.
When implementing one of the circular adapter, you will notice that instead of implementing instantiateItem, you will have to implement instantiateVirtualItem. For the fragment's pager adapter, you will implement getVirtualItem instead of getItem. That is because I've created the concept of virtual items.
To make it clear, imagine a view pager with 4 items, giving that each item represents a music. When you go all the way to left, you will see the 4th item in the left of the first. Actually, it's a whole new item, but it's linked to the virtual item that represents the 4th music.
Another example: imagine there's only one music now. You will see the same music on the left and on the right. There're 3 items at a time, but only one virtual item.
So, as explained, the Wrapper is tricking the ViewPager, making it think that there are a lot of items. To make it more difficult for the user to reach one of the ends of the ViewPager (it'd take a long time anyway), everytime a change happens to the dataset, the ViewPager goes to the same virtual item, but to one of the real items near the middle.
One more important thing is that the CircularViewPager has the method setCurrentVirtualItem. This method calculates which real item is the nearest desired virtual item and then it uses the setCurrentItem to set it. You have also the option to use the getCurrentVirtualItem, that will return the index of the current virtual item. Notice that if you use getCurrentItem, you'll get a large index.
Well, this is it. I'm sorry for the lack of documentation of the project. I'm planning document it soon. I'm also planning to remove the need for the wrapper. Feel free to copy the code (respecting the Apache 2.0 license), to fork or even contribute to it.
**If you want to make 3 views visible at same time and make it circular**
public abstract class CircularPagerAdapter extends PagerAdapter{
private int count;
int[] pagePositionArray;
public static final int EXTRA_ITEM_EACH_SIDE = 2;
private ViewPager.OnPageChangeListener pageChangeListener;
private ViewPager viewPager;
public CircularPagerAdapter(final ViewPager pager, int originalCount ) {
super();
this.viewPager = pager;
count = originalCount + 2*EXTRA_ITEM_EACH_SIDE;
pager.setOffscreenPageLimit(count-2);
pagePositionArray = new int[count];
for (int i = 0; i < originalCount; i++) {
pagePositionArray[i + EXTRA_ITEM_EACH_SIDE] = i;
}
pagePositionArray[0] = originalCount - 2;
pagePositionArray[1] = originalCount -1;
pagePositionArray[count - 2] = 0;
pagePositionArray[count - 1] = 1;
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageSelected(final int position) {
if(pageChangeListener != null)
{
pageChangeListener.onPageSelected(pagePositionArray[position]);
}
pager.post(new Runnable() {
#Override
public void run() {
if (position == 1){
pager.setCurrentItem(count-3,false);
} else if (position == count-2){
pager.setCurrentItem(2,false);
}
}
});
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrolled(pagePositionArray[position],positionOffset,positionOffsetPixels);
}
}
public void onPageScrollStateChanged(int state) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
#Override
public int getCount() {
return count;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return false;
}
public abstract Object customInstantiateItem(ViewGroup container, int position);
public void setPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener)
{
this.pageChangeListener = pageChangeListener;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int pageId = pagePositionArray[position];
return customInstantiateItem(container,pageId);
}
#Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
public void setFirstItem()
{
viewPager.setCurrentItem(EXTRA_ITEM_EACH_SIDE - 1);
}
}