I have four layouts inside of my viewpager that are supposed to match width/height of its parent (the viewpager). When the activity starts the first page appears to do that, but as soon as you swipe to the next one this weird gap appears. The gap will stay until the activity is restarted. How do I get rid of this gap?
Here's a video of what's happening: https://youtu.be/EA51HZuFruY
XML of one of the layouts (the other three are identical only having different background colors)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:background="#color/blue">
<Button
android:id="#+id/tab1_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:onClick="tab1Click"/>
</RelativeLayout>
Code for the viewpager's adapter
private class TabPagerAdapter extends PagerAdapter {
private ArrayList<Integer> layouts = new ArrayList<>();
TabPagerAdapter() {
layouts.add(R.layout.tab_menu_1);
layouts.add(R.layout.tab_menu_2);
layouts.add(R.layout.tab_menu_3);
layouts.add(R.layout.tab_menu_4);
}
#Override
public int getCount() {
return layouts.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == (View) object;
}
#Override
public Object instantiateItem(ViewGroup collection, int position) {
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(layouts.get(position), collection, false);
collection.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
collection.removeView((View) view);
}
}
EDIT: My layouts inside the viewpager are not matching parent if more than two views are in the viewpager. I can also tell it's not the viewpager not keeping it's constraints that I've given them.
EDIT: After about a month I figured out that changing the offscreen page limit to support the maximum number of screen that could ever be offscreen stops the problem. While I'd be fine with this it's causing slow down in the app, but maybe this might reveal the actually problem.
Related
I have 3 different viewgroups that need to be added in a LinearLayout. I'm using addView() to add it.
However, the adding is based on the response that my web service is returning. If there is no data, it will make a callback to the UI that the view will be empty.
Essentially, there are 3 views which are Featured, Latest and Categories. I want Featured to be at the top, followed by Latest and Categories.
I'm calling the web service like so,
public void loadFromApis() {
dealsService.getFeaturedDeals(this);
dealsService.getLatestDeals(this);
dealsService.getDealsCategories(this);
}
Example of successful callback (with data) and view adding:
#Override
public void onFeaturedSuccess(List<FeaturedModel> model) {
View view1 = DealsPanel.build(this, model);
linearLayout.addView(view1, 0);
}
#Override
public void onLatestSuccess(List<LatestModel> model) {
View view2 = DealsPanel.build(this, model);
linearLayout.addView(view2, 1);
}
#Override
public void onCategoriesSuccess(List<CategoriesModel> model) {
View view3 = DealsPanel.build(this, model);
linearLayout.addView(view3, 2);
}
I've tried using the index parameter to set the position, but since I'm loading the view based on API response, the layout wouldn't know which view to be draw first, so initializing the index would result in IndexOutOfBoundsException error.
My question is, based on this requirements, how can I statically define the position of each view to be added first and so forth? Any suggestions on improving the structure of this code?
Thanks in advance
One approach would be to statically define 3 child layouts in code or in XML inside of your parent LinearLayout, and then add your new views to the child layouts. That will preserve their order. For example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout android:id="#+id/featuredDealsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout android:id="#+id/latestDealsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout android:id="#+id/dealCategoriesLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Then, assuming you initialize variables containing the new layouts (ie featuredDealsLayout) you could change your code to something like:
#Override
public void onFeaturedSuccess(List<FeaturedModel> model) {
View view = DealsPanel.build(this, model);
featuredDealsLayout.addView(view);
}
#Override
public void onLatestSuccess(List<LatestModel> model) {
View view = DealsPanel.build(this, model);
latestDealsLayout.addView(view);
}
#Override
public void onCategoriesSuccess(List<CategoriesModel> model) {
View view = DealsPanel.build(this, model);
dealCategoriesLayout.addView(view);
}
I've been trying to use ViewPagerAndroid to show a carousel with "stops" at each item like so:
It appears react-native-carousel and react-native-swiper don't support these features yet on Android. I have a native ViewPager that does what I need it to do, but I'm having problems incorporating it into the React Native environment.
The native component CarouselContainer uses layout.xml and expects a ViewPager subcomponent.
<com.mycompany.ui.CarouselContainer
android:id="#+id/pager_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="190dp"
android:layout_height="280dp"
android:layout_gravity="center_horizontal" />
</com.mycompany.ui.CarouselContainer >
CarouselContainer is a FrameLayout subclass that gets a ref to the ViewPager in its onFinishInflate method. It calls setPageMargin on the ViewPager and setClipChildren(false) to render multiple ViewPager pages on the screen at the same time.
I took a look at the ViewPagerAndroid implementation, and it looks like it's basically a ViewPager set up by ReactViewPagerManager. The ReactViewPagerManager exposes the ViewPager's child views via the ViewGroupManager interface.
So I copied ReactViewPagerManager in my own CarouselViewPagerManager and ReactViewPager into CarouselViewPager.
The one thing new here is CarouselViewPagerContainer. The manager creates the container and the container creates the view pager instead of the manager creating the view pager directly. Since there's no layout xml the container instantiates the pager directly instead of getting it from onFinishInflate.
For some reason, I'm getting nothing rendered on the screen when I use it. I think it's due to having the CarouselViewPagerContainer view in between but I'm not sure where to even begin. Do I need to mess with LayoutShadowNode? Do I need to add the CarouselViewPager itself to the list of child views returned by the manager?
The Native UI Components documentation only mentions SimpleViewManager. :(
// Copy of ReactViewPagerManager
public class CarouselViewPagerManager extends ViewGroupManager<CarouselViewPagerContainer> {
#Override
protected CarouselViewPagerContainer createViewInstance(ThemedReactContext reactContext) {
return new CarouselViewPagerContainer(reactContext);
}
// ...
#Override
public void addView(CarouselViewPagerContainer parent, View child, int index) {
parent.addViewToAdapter(child, index);
}
#Override
public int getChildCount(CarouselViewPagerContainer parent) {
// Should the CarouselViewPager be also counted?
return parent.getViewCountInAdapter();
}
#Override
public View getChildAt(CarouselViewPagerContainer parent, int index) {
return parent.getViewFromAdapter(index);
}
#Override
public void removeViewAt(CarouselViewPagerContainer parent, int index) {
parent.removeViewFromAdapter(index);
}
}
public class CarouselViewPagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private final CarouselViewPager mPager; // Copy of ReactViewPager
public CarouselViewPagerContainer(ReactContext context) {
mPager = new CarouselViewPager(context);
}
// ...
// Pass through to the CarouselViewPager
public void setCurrentItemFromJs(int item, boolean animated) {
mPager.setCurrentItemFromJs(item, animated);
}
/*package*/ void addViewToAdapter(View child, int index) {
mPager.addViewToAdapter(child, index);
}
/*package*/ void removeViewFromAdapter(int index) {
mPager.removeViewFromAdapter(index);
}
/*package*/ int getViewCountInAdapter() {
return mPager.getViewCountInAdapter();
}
/*package*/ View getViewFromAdapter(int index) {
return mPager.getViewFromAdapter(index);
}
}
If you know of a simpler way to tackle this problem I'm all ears!
I mean the only thing I'm using the FrameLayout container for is to center the first Viewpager page and occupy space so multiple pages show. :/
It's been such a struggle :(
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 am using A custom viewpager with a PagerTitleStrip, both supported by the android compatibility package. As recommended I use it like this:
<snok.stubefrie.DayPager
android:id="#+id/viewpager"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
<android.support.v4.view.PagerTitleStrip
android:id="#+id/strip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"/>
</snok.stubefrie.DayPager>
However the titlestrip overlaps with the actual content of my viewpager (Both starting at 0,0). Any ideas?
How do you add views to ViewPager?
When I met the same problem, the cause was to add views with wrong position in my PagerAdapter implementation.
The bad code was like this.
viewPager.setAdapter(new PagerAdapter() {
#Override
public Object instantiateItem(ViewGroup pager, int position) {
View view = createView(position);
((ViewPager) pager).addView(view, position);
return view;
}
#Override
public void destroyItem (ViewGroup pager, int position, Object view) {
((ViewPager) pager).removeViewAt(position);
}
...
});
This code had been working, however, when I began to use PagerTitleStrip, the PagerTitleStrip view became the first child of the ViewPager, hence addView(view, 0) broke the internal structure of the ViewPager.
It was fixed by replacing addView and removeView as below.
addView(view, position) -> addView(view)
removeViewAt(position) -> removeView((View) view)
Hope it helps!
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.page_view, null);
((ViewPager) collection).addView(view);
getPageTitle(position);
return view;
}
Just remove index of addView method. it solve overlapping problem of strip.
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