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.
Related
I'm trying to remove a view from a Container of type ViewGroup like below, and I cast it into a ViewPager, but I noticed that the removeView() function is not working, as at now, I'm not sure of what I'm doing wrong.
I need help.
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
ViewPager viewPager = (ViewPager)container;
View view = (View)object;
ViewPager.removeView(view); //removeView is not working
layoutInflater = (LayoutInflater)context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
View view=layoutInflater.inflate(R.layout.custom_layout,null);
ImageView imageView = (ImageView) view.findViewById(R.id.myImageView);
imageView.setImageResource(images[position]);
ViewPager viewPager = (ViewPager)container;
ViewPager.addView(view);
return view;
Yeah. It is works as expected. Instead of doing above, you need to remove the item for the ViewPager adapter. Something like below.
// Delete a page at a `position`
public void deletePage(int position) {
// Remove the corresponding item in the data set
pageIndexes.remove(position);
// Notify the adapter that the data set is changed
notifyDataSetChanged();
}
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.
I deleted almost all code in my project to find a hiding bug. There was a GridView that containing a frame layout, and the layout contained CheckBox. But I couldn't check the first check box.(others worked)
Finally (I think) I found an answer. But this is so weird. When I deleted lines for recycling convertView, the bug was gone. I changed from :
if(convertView == null) {
layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
} else {
layout = (FrameLayout) convertView;
}
to FrameLayout layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);.
I really have no idea of this stuation. I attach rest codes.
TasteGridAdapter.java:
public class TasteGridAdapter extends BaseAdapter {
Context maincon;
public TasteGridAdapter(Context context) {
maincon = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FrameLayout layout;
if(convertView == null) {
layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
} else {
layout = (FrameLayout) convertView;
}
layout.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
return layout;
}
#Override
public int getCount() {
return 3;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
}
onCreate of the activity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.taste);
TasteGridAdapter adapter = new TasteGridAdapter(this);
GridView grid = (GridView) findViewById(R.id.taste_grid);
grid.setAdapter(adapter);
}
taste.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<GridView
android:layout_weight="1"
android:id="#+id/taste_grid"
android:layout_width="match_parent"
android:layout_height="0dip"
android:columnWidth="87dip"
android:gravity="center"
android:horizontalSpacing="4dip"
android:numColumns="auto_fit"
android:padding="2dip"
android:stretchMode="columnWidth"
android:verticalSpacing="4dip" />
</LinearLayout>
taste_brand.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="87dp"
android:layout_height="58dp">
<CheckBox
android:id="#+id/taste_brand_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</FrameLayout>
I encountered similar problem with first item in GridView. To resolve issue, remove 'new' keyword, and change existing views LayoutParams like that:
LayoutParams lp = layout.getLayoutParams();
lp.height = someHeight;
...do something with these LayoutParams. This hack resolves my issues. Conclusion, try to avoid creation of new LayoutParams object through "new".
layout.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
put this line in this condition,
if(convertView == null) {
}
I was faced same problem, but i try this and its work for me. I hope it also work for you.
What you're experiencing has to do with the way Android recycles views in ListView, GridView, etc. You mention that your first checkbox is uncheckable, while your others remain working. I think you'll notice that the others only appear to work properly, since you haven't handled the recycling properly.
The reason your line
FrameLayout layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
seems to fix the problem is because this now inflates the views again each time they are used. I'll admit, when I started with this, re-inflating the views seemed to be the best solution; it entirely defeats the purpose of recycling, however, and you lose all the performance benefits otherwise gained.
So now to fix your problem:
First, I highly recommend using the ViewHolder pattern in conjunction with your BaseAdapter. More information on that can be found here.
Second, you should probably create a boolean array to match all the items in your GridView, and use it to determine whether or not an item should be clicked. Set the value of the corresponding boolean inside your checkbox listener and use that value inside getView(..) to check or uncheck that particular box.
An overall better solution might be to use an array (or list) of models inside your adapter class, each of these containing a boolean field accessible through isChecked and setChecked(boolean). Again, you would use this inside your getView(..) to display the views properly and change the value inside your checkbox OnCheckedChangeListener.
Hope that helps.
As jonstaff says, it's to do with View recycling.
If you're using a custom Adapter class for your GridView View binding, try modifying its getView() method to always instantiate a new View like:
public View getView(int position, View convertView, ViewGroup parent){
SomeView v = new SomeView(context); // <--- here
...
return v;
}
Instead of the typical:
public View getView(int position, View convertView, ViewGroup parent){
SomeView v;
if (convertView == null)
v = new SomeView (context);
else
v= (SomeView)convertView;
...
return v;
}
This may affect performance, but it solved my problem for a small GridView of Buttons.
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