I am creating dynamic ViewPager as below : pls. check :
/** adding gridView created to main root layout.*/
pager=new ViewPager(this);
pagerAdapter = new KeyboardPagerAdapter();
pagerAdapter.addView (mGridImages, 0);
pagerAdapter.addView (mGridTextImages, 1);
pager.setAdapter (pagerAdapter);
ViewGroup.LayoutParams params = mGridImages.getLayoutParams();
params.height = mDeviceHeight;
pager.setLayoutParams(params);
linRootLayout.addView(pager);
return linRootLayout;
My Class KeyboardPagerAdapter is as below :
public class KeyboardPagerAdapter extends PagerAdapter{
// This holds all the currently displayable views, in order from left to right.
private ArrayList<View> views = new ArrayList<View>();
//-----------------------------------------------------------------------------
// Used by ViewPager. "Object" represents the page; tell the ViewPager where the
// page should be displayed, from left-to-right. If the page no longer exists,
// return POSITION_NONE.
#Override
public int getItemPosition (Object object)
{
int index = views.indexOf (object);
if (index == -1)
return POSITION_NONE;
else
return index;
}
// Used by ViewPager. Called when ViewPager needs a page to display; it is our job
// to add the page to the container, which is normally the ViewPager itself. Since
// all our pages are persistent, we simply retrieve it from our "views" ArrayList.
#Override
public Object instantiateItem (ViewGroup container, int position)
{
View v = views.get (position);
container.addView (v);
return v;
}
// Used by ViewPager. Called when ViewPager no longer needs a page to display; it
// is our job to remove the page from the container, which is normally the
// ViewPager itself. Since all our pages are persistent, we do nothing to the
// contents of our "views" ArrayList.
#Override
public void destroyItem (ViewGroup container, int position, Object object)
{
container.removeView (views.get (position));
}
// Used by ViewPager; can be used by app as well.
// Returns the total number of pages that the ViewPage can display. This must
// never be 0.
#Override
public int getCount ()
{
return views.size();
}
// Used by ViewPager.
#Override
public boolean isViewFromObject (View view, Object object)
{
return view == object;
}
// Add "view" to right end of "views".
// Returns the position of the new view.
// The app should call this to add pages; not used by ViewPager.
public int addView (View v)
{
return addView (v, views.size());
}
//-----------------------------------------------------------------------------
// Add "view" at "position" to "views".
// Returns position of new view.
// The app should call this to add pages; not used by ViewPager.
public int addView (View v, int position)
{
views.add (position, v);
return position;
}
//-----------------------------------------------------------------------------
// Removes "view" from "views".
// Retuns position of removed view.
// The app should call this to remove pages; not used by ViewPager.
public int removeView (ViewPager pager, View v)
{
return removeView (pager, views.indexOf (v));
}
//-----------------------------------------------------------------------------
// Removes the "view" at "position" from "views".
// Retuns position of removed view.
// The app should call this to remove pages; not used by ViewPager.
public int removeView (ViewPager pager, int position)
{
// ViewPager doesn't have a delete method; the closest is to set the adapter
// again. When doing so, it deletes all its views. Then we can delete the view
// from from the adapter and finally set the adapter to the pager again. Note
// that we set the adapter to null before removing the view from "views" - that's
// because while ViewPager deletes all its views, it will call destroyItem which
// will in turn cause a null pointer ref.
pager.setAdapter (null);
views.remove (position);
pager.setAdapter (this);
return position;
}
//-----------------------------------------------------------------------------
// Returns the "view" at "position".
// The app should call this to retrieve a view; not used by ViewPager.
public View getView (int position)
{
return views.get (position);
}
}
My issue is that, When I launch my app, The first page inside is displayed ok. But, Second pager is displayed as blank : white page.
What might be the issue ? Why PagerAdapter not taking second view as i have done as
pagerAdapter.addView (mGridImages, 0);
pagerAdapter.addView (mGridTextImages, 1);
1 > to add second view.
Pls. check and guide me.
Thanks.
Its my mystake. As Global I have setted the Visibility of my second layout to invisible.
Related
this question is related with this one that I asked before.
I create a viewpager in MainActivity.java like this:
final ViewPager viewPager = (ViewPager) findViewById(R.id.vp_horizontal_ntb);
viewPager.setAdapter(new PagerAdapter() {
#Override
public int getCount() {
return 5;
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}
#Override
public void destroyItem(final ViewGroup container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
if(position==0) {
// here is important!
} else if(position == 1) {
}
...
}
});
Now I want fill each page with some json RecyclerView data list.(get json from network).
each page has independent data list.
For first time, I create a fragment for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getActivity().getBaseContext()).inflate(R.layout.fragment_tab0, null, false);
tabFragment.MyAdapter adapter = new tabFragment.MyAdapter(getActivity().getSupportFragmentManager());
adapter.addFragment(new tab0Fragment(), getResources().getString(R.string.tab0));
container.addView(view);
return view;
}
(so for 5 page, I have 5 fragment.
DEMO
But My application run slow (laggy) when I swipe pages.(with tap buttom is normal)
So I tried write an Adapter class directly for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getBaseContext()).inflate(R.layout.item_vp_list, null, false));
final RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.rv);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(new Tab0RecycleAdapter());
container.addView(view);
return view;
}
with top code,my application run fast again with swap pages!
Is it important to create fragment per each page?
why I must use fragment?(because some programmer recommended it in viewpager)
my method (second method without fragment) is true or false for a real application?
(I am noob and this is my first app)
Now I want fill each page with some json RecyclerView data list.(get json from network).
If you perform this network task on the UI thread, it will block and could cause laggy performance. This could be the reason your pages load slowly. You should perform network tasks on a separate thread.
So I tried write an Adapter class directly for each page like this
You only need one adapter per recycler view. If you want to support multiple views within the same adapter, override getItemViewType(). Example here: https://stackoverflow.com/a/26245463/7395923
Is it important to create fragment per each page?
Why I must use fragment? (because some programmer recommended it in view pager)
It is possible to use a view pager without fragments. Base on your previous question (linked at the top), it does seem overkill to load an entire fragment just to inflate a view. Here is a link to an example of a view pager without fragments: https://stackoverflow.com/a/18710626/7395923
I hope this helps.
I have a very simple PagerAdapter that for some reason, removes the views at position 0 and
1 when it is scrolled to position 2.
Specifically, when I first run the app, there are 3 views. I scroll to position 2 and position 1's view will disappear. View 0 is still there. If I scroll to view 0 and back to view 2 and again back to view 0, View 0 suddenly disappears. I do the same again, and I can actually see view 0 being instantiated and immediately destroyed.
what is going on here?
Main Activity
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyPagerAdapter adapter = new MyPagerAdapter(this);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
}
public class MyPagerAdapter extends PagerAdapter {
private Context ctx;
Calendar c = Calendar.getInstance();
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
ViewGroup collection;
public MyPagerAdapter (Context ctx){
this.ctx = ctx ;
}
#Override
public int getCount() {
return 3;
}
public Object instantiateItem(ViewGroup container, int position ){
this.collection = (ViewPager)container;
NewMonth monthObject = new NewMonth(ctx, month+position-1,2012);
View monthLayout = monthObject.newParentLayout;
collection.addView(monthLayout);
return monthLayout;
return addViewAt(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
collection.removeViewAt(position);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;
}
}
You have to maintain all tabs in memory specifying the OffscreenPageLimit to N-1, in your case put this inside the onCreate method:
myPager.setOffscreenPageLimit(2);
Checkout populate() function from ViewPager source - it has clear checks then to remove currentIndex+1 and currentIndex-1 items. Removing is done based on view sizes and it seems to be entirely internal ViewPager logic. ViewPager source is located
<android sdk folder>\extras\android\support\v4\src\java\android\support\v4\view\ViewPager.java
.
You might debug ViewPager to know that is happening exactly, but I wouldn't suggest so until You faced some serious issue with ViewPager. What's why: if you dig into ViewPager code it might lead You to write some hackish code (even not intentionally) on it based on its internal structure and not on its public interface and documentation. So, the main idea of data encapsulation would be ruined which is definitely not good (sadly, sometimes we need to do so in Android due to lack of documentation / unclear naming / android internal issues etc., check Code Complete by Steve McConnell for more details on good encapsulation etc.).
Here the key is that position is different then index. If they give you a position there is no guarantee that your collections haven't removed other positions.
Example:
Let's say your at position 2. destroyItem might have been called for position 0 which means in your collections now position 2 is actually at index 1. So your indexes will quickly become out of sync from your positions. The same thing happens with listview children. I would recommend using a sparseArray instead.
private final SparseArray<View> views = new SparseArray<>();
public View instantiateItem(ViewGroup container, final int position) {
...
views.put(position, view);
...
}
public void destroyItem(ViewGroup container, int position, Object object) {
View view = views.get(position);
if (view != null) {
container.removeView(view);
views.remove(position);
}
}
I'm trying to use a view pager to make a gallery. Nevertheless it's always the view at the position 1 which is updated whereas i'm trying to update the current page (0 by default).
First of all i have created a view which gonna be inflated by the pager. This view is composed of 4 ImageView and 4 textview.
Then i created a adapter for the pager :
public class LivePagerAdapter extends PagerAdapter {
private CameraList cameras = null;
public LivePagerAdapter(CameraList cams)
{
this.cameras = cams;
}
#Override
public int getCount() {
return this.cameras.size() /4; // 4 imageview in a page
}
public void setCameras(CameraList cams)
{
this.cameras = cams;
}
#Override
public Object instantiateItem(View container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
View view = inflater.inflate(R.layout.pager,null);
((ViewPager) container).addView(view,0);
return view;
}
}
Finally in my activity i'm instanciate the pager like this :
adapter = new LivePagerAdapter(this.listeCamera);
pager = (ViewPager) findViewById(R.id.camspager);
pager.setAdapter(adapter);
pager.setCurrentItem(0);
After that i'm initialising a array with my 4 imageview :
tabImageView.add((ImageView) findViewById(R.id.cam1)); //Imageview located in the view loaded by the pager
tabImageView.add((ImageView) findViewById(R.id.cam2));
tabImageView.add((ImageView) findViewById(R.id.cam3));
tabImageView.add((ImageView) findViewById(R.id.cam4));
And when needed i add a image in the imageview.
It seems to works except that i'm asking the pager to start at position 0 and this is the view from position 1 which is updated with my images.
When i use findViewById(somethingInflatedByThePager) i need it to be the current page , how can i do that ?
What happens if you replace ((ViewPager) container).addView(view,0); by ((ViewPager) container).addView(view);
I hope I can show 3 items in a page of viewpager, but now I only could set viewpager's padding value and margin value, so it only show one item in a page of viewpager. How can I set the item width? I think if I can set more little width of item, the viewpager will show more items in a page.
Quick Answer
What you want is overriding getPageWidth in the PagerAdapter implementation you made for that ViewPager.
For example setting it to 0.8f will make a single page cover only 80% of the viewpagers width so that part of the next and/or previous pages are visible.
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
More information at https://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getPageWidth(int)
Other remarks
Also note that the negative margins will only cause the separate pages to overlap each other. The margin is used to define the space between each page.
If no alternative pageWidth is configured (there is no setter, only the override) it will cover 100% by default making no part of the other pages visible unless dragged.
Code example
A very nice overview of what is possible can be found here https://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
The view pager in your layout
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
Example of an inline configured Adapter implementation, this would go in the onCreate or onViewCreated methods:
// get a reference to the viewpager in your layout
ViewPager mViewPager = (ViewPager) findViewById(R.id.viewpager);
// this margin defines the space between the pages in the viewpager mViewPager.setPageMargin(context.getResources().getDimensionPixelSize(R.dimen.margin_normal));
// setting the adapter on the viewpager, this could/should be a proper Adapter implementation in it's own class file instead
mViewPager.setAdapter(new PagerAdapter() {
// just example data to show, 3 pages
String[] titles = {"Eins", "Zwei", "Drei"};
int[] layouts = {R.layout.layout1, R.layout.layout2, R.layout.layout3};
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
// here you can inflate your own view and dress up
ViewGroup layout = (ViewGroup) inflater.inflate(layouts[position], container, false);
container.addView(layout);
return layout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return layouts.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
});
Example code based on https://newfivefour.com/android-viewpager-simple-views.html
Have you tried setting the page margins to a negative value, see setPageMargin(int)? If I remember correctly, I read someone realizing something similar to what you're describing that way.
Alternatively, you could have a look at using a Gallery in stead, although I have to admit I'm not a big fan of them as they seem to be less flexible and more buggy.
I'm designing an app that allows users to flip between multiple pages in a ViewPager. I've been struggling trying to figure out how it is possible to remove a Fragment instance from a page when it is no longer visible on screen, cache it (to, say, a HashMap), and then restore it so that when the user flips back to that page, the views and everything else in it will be in the same state it was before removal. For example, my first page is a login screen that makes certain layout elements on that particular page visible/invisible on a successful login. When I flip forward enough pages then flip back to the first page, the layout is reset. This becomes more of a problem for another one of my pages which contains a huge, horizontal/vertical scrolling grid of data that I use a thread in the background to draw when it initializes. I use a progress dialog to notify the user of loading progress and that becomes really annoying everytime I have to load it.
So I did some research...
I browsed through the source code for FragmentStatePageAdapter and in the destroyItem() callback, the state of the Fragment instance being removed is saved to an ArrayList. When a new instance of the Fragment is being created in the instantiateItem() callback, if an instance of an item doesn't already exist (they keep track of this by using an ArrayList), a new Fragment instance is created and its saved state is initialized with the corresponding Fragment.SavedState data. Unfortunately, this data does not include the state that the Views were in although I noticed that for pages with a GridView/ListView, the state of the Views were somehow restored (if I scrolled to some random position, flipped a few pages and came back, it would not be reset).
According to the API:
The saved state can not contain dependencies on other fragments --
that is it can't use putFragment(Bundle, String, Fragment) to store a
fragment reference because that reference may not be valid when this
saved state is later used. Likewise the Fragment's target and result
code are not included in this state.
Being a noob to Android, I'm not quite sure I understand the last statement.
That being said, is there any way to cache View state? If not, I think I'll just go ahead and go with leaving all the fragment pages in memory.
I had the same problem problem and solved it by implementing these two functions
public void onSaveInstanceState (Bundle outState)
public void onActivityCreated (Bundle savedInstanceState)
on the fragments that I wanted to save. On the first function, you should save in the Bundle the date that you need to restore the views ( in my case I had a bunch of spinner so I used
an int array to save their positions). The second function, which is called when restoring your fragment, is where you implement the restoring process.
I hope this helps. I also made my adapter to inherit from FragmentStatePageAdapter but I am not sure that this is mandatory.
Listing of main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:text="Page 1" android:id="#+id/textViewHeader"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:gravity="center" android:padding="10dip" android:textStyle="bold"></TextView>
<android.support.v4.view.ViewPager
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:id="#+id/viewPager" />
</LinearLayout>
Setting up the ViewPager
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
MyPagerAdapter adapter = new MyPagerAdapter(this);
viewPager.setAdapter(adapter);
The PagerAdapter
#Override
public void destroyItem(View view, int arg1, Object object) {
((ViewPager) view).removeView((View)object);
}
#Override
public int getCount() {
return views.size();
}
#Override
public Object instantiateItem(View view, int position) {
View view = views.get(position);
((ViewPager) view).addView(view);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
look here for more details view pager example
Looking at the various documentation pieces, my best guess is that the views you are creating do not have an ID attached to them. Assuming that the fragment's saved state is created from Fragment.onSaveInstanceState, then the fragment will automatically save any view's state that has an id. You probably have a default id associated with your ListView/GridView if you created them from a layout file. You can also associate an id with the views by calling setId.
Also, for your custom filled fragment, you may also have to do something custom in onSaveInstanceState.
Here's an example of how I implemented caching in PagerAdapter. After filling the cache all future view requests are served from cache, only data is replaced.
public class TestPageAdapter extends PagerAdapter{
private int MAX_SIZE = 3;
private ArrayList<SoftReference<View>> pageCache = new ArrayList<SoftReference<View>>(3);
public TestPageAdapter(Context context){
// do some initialization
}
#Override
public int getCount() {
// number of pages
}
private void addToCache(View view){
if (pageCache.size() < MAX_SIZE){
pageCache.add(new SoftReference<View>(view));
} else {
for(int n = (pageCache.size()-1); n >= 0; n--) {
SoftReference<View> cachedView = pageCache.get(n);
if (cachedView.get() == null){
pageCache.set(n, new SoftReference<View>(view));
return;
}
}
}
}
private View fetchFromCache(){
for(int n = (pageCache.size()-1); n>= 0; n--) {
SoftReference<View> reference = pageCache.remove(n);
View view = reference.get();
if (view != null) {
return view;
}
}
return null;
}
#Override
public Object instantiateItem(View collection, int position) {
View view = fetchFromCache();
if (view == null) {
// not in cache, inflate manually
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.page, null);
}
setData(view, position);
((ViewPager) collection).addView(view, 0);
return view;
}
private void setData(View view, int position){
// set page data (images, text ....)
}
public void setPrimaryItem(ViewGroup container, int position, Object object) {
currentItem = (View)object;
}
public View getCurrentItem() {
return currentItem;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public void destroyItem(View collection, int arg1, Object view) {
((ViewPager) collection).removeView((View) view);
addToCache((View) view);
}
}
I also ran into this problem when I was using PagerSlidingTabStrip and using and instance of FragmentPagerAdapter, switching to FragmentStatePagerAdapter definitely worked.
Then I use onSaveInstanceState() to save sate