I am creating an ArrayAdapter for my gridView considering header and footer views.
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<GridView
android:id="#+id/activity_main_gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="4" />
</RelativeLayout>
The problem is that when I scroll to the top or the bottom of the grid header/bottom views get over grid items and, when clicking on any item it goes back to the original position (I will put screenshots if necessary)
This is my GridView adapter (params in order: context, arraylist of grid items(without headers/footers), header view for all first row, footer view for all last row, gridview columns number):
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGridView = (GridView) findViewById(R.id.activity_main_gridview);
List<String> list = new ArrayList<String>();
for (int i = 0; i < 500; i++) {
list.add("item " + i);
}
View headerView = new View(this);
headerView.setMinimumHeight(70);
headerView.setBackgroundColor(Color.argb(255, 63, 81, 181));
GridHeaderFooterAdapter adapter =
new GridHeaderFooterAdapter(this, list, headerView, headerView, 4);
mGridView.setAdapter(adapter);
}
private final class GridHeaderFooterAdapter extends ArrayAdapter<String> {
private int mNumColumns;
private int mListSize;
private List<String> mList;
private View mHeaderView;
private View mFooterView;
private LayoutInflater mInflater;
public GridHeaderFooterAdapter(ActivityMain context, List<String> list, View headerView, View footerView, int numColumns) {
super(context, 0);
this.mNumColumns = numColumns;
this.mListSize = list.size();
this.mList = list;
this.mHeaderView = headerView;
this.mFooterView = footerView;
mInflater = context.getLayoutInflater();
}
#Override
public int getCount() {
int count = 0;
//headers
count += mNumColumns;
//list items
count += mListSize;
//footers
count += mNumColumns + (mNumColumns - mList.size() % mNumColumns);
Log.w("ActivityMain", "getCount() = " + count);
return count;
}
#Override
public String getItem(int position) {
//discard header items
return mList.get(position - mNumColumns);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//headers
if (position < mNumColumns){
Log.w("ActivityMain", "headers: position = " + position);
return mHeaderView;
}
//listitems
if (position >= mNumColumns && position < mNumColumns + mListSize) {
Log.w("ActivityMain", "listitems: position = " + position);
LinearLayout ll = (LinearLayout) mInflater.inflate(R.layout.listitem_main, null);
TextView tv = (TextView) ll.findViewById(R.id.textView);
tv.setText(getItem(position));
return ll;
}
//footers
if (position >= mNumColumns + mListSize){
Log.w("ActivityMain", "footers: position = " + position);
/*TODO: some footers might be in the same row as listitem, which is wrong because Gridview uses last item of the row to determine row height... */
return mFooterView;
}
return null;
}
}
BEFORE SCROLLING:
AFTER SCROLLING:
As you can see, the target is to add a header to avoid first items being behind the status bar
Manually playing around with the position numbers is a dangerous thing, though quite possible if done correctly. Problem here is that you are trying to bypass how the getView() method operates. getView() will maintain it's own reference to the returned View and recycle or destroy it accordingly. It's already been hugely optimized to provide great performance, and to achieve your end goal, you want to work with it...not against.
Ultimately, you want a spacing for the 1st (header) and last (footer) row in your GridView to avoid being overlapped with the translucent status/navigaion bars. I'll give you a higher overview of what needs to happen.
getCount() needs to return:
mListSize + (mNumColumns * 2)
Why? Because you'll need a header view for every column and a footer view for every column as well.
getItem() needs to return something for all possible positions. That means whatever value returned bygetCount() needs to be able to return something...even if it's an empty string. So something like:
// if a header position or a footer position
if (position < mNumColumns || position > mListSize + mNumColumns) {
return ""
} else {
return mList.get(position - mNumColumns);
}
Note, this may be off by a little considering count is not zero based but position is...but it shows the basic gist of what you need.
getView() needs to render a view as normal. Difference being that if the position number is indicative of a header/footer position, you return a blank view. So for example:
ViewHolder vh = null;
if (convertView == null) {
//inflate your view
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView;
}
//If position is indicative of a header/footer. Alternatively you could see if getItem() returns an empty string...assuming your data can never contain an empty string.
if (position < mNumColumns || position > mListSize + mNumColumns) {
//Don't fill the view with any data. Make any visible elements INVISIBLE
} else {
//populate convertView with data
}
Get rid of the whole notion of passing in a view for the adapter to use. That will def break everything. If performance is very much a concern then a better alternative (and the proper one...more so then what I posted) is to use getItemViewType() and getViewTypeCount(). However that will require you to create a class object that can represent your actual data (eg Strings) and a flag that indicates it's a header, footer, and the data.
Related
I have a list view which uses a custom ArrayAdapter. The items of the ListView are RelativeLayouts. The "Light" views which are stored in "lightsOnThisTrack" list of a "Track" object are added afterward to its corresponding RelativeLayouts.
The thing is that if I add more items to the ListView, the views that were previously added to the relativeLayouts start to repeat on the newly added items. On the other hand, the TextView "trackText" is not being repeated, as can be seen in the example. As I've read on other posts, I know that it's a problem related to the way the ViewHolder pattern is implemented, but I cannot spot where the problem is.
public class TrackListAdapter extends ArrayAdapter<Track> {
private static final String TAG = "TrackListAdapter";
private LayoutInflater layoutInflater;
public ArrayList<Track> trackArrayList;
Context mContext;
RelativeLayout relativeLayout;
public TrackListAdapter(Context context, ArrayList<Track> trackArrayList) {
super(context, 0, trackArrayList);
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mContext = context;
this.trackArrayList = trackArrayList;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View rowView = convertView;
ViewHolder viewHolder;
if (rowView == null) {
rowView = layoutInflater.inflate(R.layout.track_list_item, null);
viewHolder = new ViewHolder();
viewHolder.relativeLayout = (RelativeLayout) rowView.findViewById(R.id.relativeLayout);
viewHolder.trackText = new TextView(mContext);
viewHolder.trackText.setTextColor(Color.GRAY);
viewHolder.trackText.setX(100);
viewHolder.trackText.setY(20);
viewHolder.trackText.setTextSize(18);
viewHolder.relativeLayout.addView(viewHolder.trackText);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
viewHolder.track = trackArrayList.get(position);
if (viewHolder.track.getName() == null)
viewHolder.trackText.setText(" NUMBER " + position);
else
viewHolder.trackText.setText(viewHolder.track.getName());
for (int i = 0; i < viewHolder.track.getNumberOfLights(); i++) {
Light light = viewHolder.track.lightsOnThisTrackList.get(i);
if (light.getParent() != null) {
if (!light.getParent().equals(viewHolder.relativeLayout)) {
ViewGroup viewGroup = (ViewGroup) light.getParent();
if (viewGroup != null) viewGroup.removeView(light);
viewHolder.relativeLayout.addView(light);
}
} else {
viewHolder.relativeLayout.addView(light);
}
}
notifyDataSetInvalidated();
notifyDataSetChanged();
return rowView;
}
public static class ViewHolder {
Track track;
TextView trackText;
RelativeLayout relativeLayout;
}
public View getViewByPosition(int pos, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition) {
return listView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
}
The problem is not the ViewHolder. The problem is that you are not taking into account what happens when your view is recycled.
Suppose for position 0 you add two Lights to the Relativelayout. Then the user scrolls and the view gets recycled to another position (let's say position 10). The RelativeLayout you are given already has two Lights in it before you do anything.
You either need to remove all the previous Lights first, or you need to be able to re-use ones that are there (and still you might have to remove some in case the row you're creating has fewer Lights than are already present).
The TextView is not repeated because you are not creating a TextView every time the view is recycled; you are only creating it when a new row is being inflated.
A few other suggestions:
There should be no reason to call notifyDataSetInvalidated() and notifyDataSetChanged() inside of getView().
I discourage the use of holding lists of Views (in this case, Lights) in your data model. You don't have a clear separation between data and presentation, and I think it will only complicate your code. It would be easier to just store how many lights a Track needs and handle the actual Views separately.
I would also try to avoid creating, adding, and removing Views inside of getView(). For instance, if you know there's a small, limited number of lights a Track can have (suppose it's five), then it's easy enough to have that many corresponding views in the row layout already and just toggle their visibility appropriately. Or, you can make a custom View that knows how to draw up to that number of lights and you just change the number inside of getView().
How may I get all children of an item of ListView ?
There are methods to get child at position but I was not able to find any method which returns collection of all children in an item of ListView.
Update : I just found out that I need ListView items(rows) instead. How may I achieve this ?
What I am doing is, comparing the selected ListView item with all items.
You get all list item from list adapter through iteration as below
for (int i=0;i<adapter.getCount();i++){
adapter.getItem(i);
}
Update : You can compare specific item using index with list adapter all items as below :
for (int i=0;i<adapter.getCount();i++){
if(adapter.get(selectedIndex)==adapter.getItem(i)){
// TODO : write code here item match
break; // after match is good practice to break loop instead compare rest of item even match
}
}
Use this method to get all insight and out-sight view of list view:
for ( int i = 0 ; i < listView.getCount() ; i++){
View v = getViewByPosition(i,listView);
}
public View getViewByPosition(int position, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition =firstListItemPosition + listView.getChildCount() - 1;
if (position < firstListItemPosition || position > lastListItemPosition ) {
return listView.getAdapter().getView(position, listView.getChildAt(position), listView);
} else {
final int childIndex = position - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
I guess if your items in the ListView are of type ViewGroup you could do the following
ArrayList<View> children = new ArrayList<View>();
for (int i = item.getChildCount() - 1 ; i>=0; i--) {
children.add(item.getChildAt(i));
}
May be you are looking for something like this. You need to have access to the rootView from which you can get the child views. onItemSelected is only used as it gives the rootview of clicked position.
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
ImageView imgView = view.findViewById(R.id.myImageView);//your imageview that is inflated inside getView() of adapter
//similarly for other views
}
The scenario
I'm trying the create something akin to the featured page on Google Play Store. But instead of showing three items for a category I'm allowing it show any number of items in a two column staggered grid view fashion.
So each list item has a header that has a title and a description followed by a custom view (lets call this SVG, as in Staggered View Group) that shows some number of children views in a staggered grid view fashion.
I have a class called FeaturedItems that hold the data for a row in the featured list. Here is an extract:
public class FeaturedItems {
private String mName;
private String mDescription;
private ArrayList<Object> mList;
public FeaturedItems(String name, String description, Object... items) {
mName = name;
mDescription = description;
mList = new ArrayList<Object>();
for (int i = 0; i < items.length; i++) {
mList.add(items[i]);
}
}
public int getItemCount() {
return mList.size();
}
public Object getItem(int position) {
return mList.get(position);
}
public String getFeatureName() {
return mName;
}
public String getFeatureDescription() {
return mDescription;
}
}
The FeaturedListAdapter binds the data with the views in the getView() method of the adapter. The getView() method is as follows:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
FeaturedItems items = getItem(position);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this.mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(mResource, null);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.list_item_shop_featured_title);
holder.description = (TextView) convertView.findViewById(R.id.list_item_shop_featured_description);
holder.svg = (StaggeredViewGroup) convertView.findViewById(R.id.list_item_shop_featured_staggered_view);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.title.setText(items.getFeatureName());
holder.description.setText(items.getFeatureDescription());
// HELP NEEDED HERE
// THE FOLLOWING PART IS VERY INEFFICIENT
holder.svg.removeAllViews();
for (int i = 0; i < items.getItemCount(); i++) {
FeaturedItem item = new FeaturedItem(mContext, items.getItem(i));
item.setOnShopActionsListener((ShopActions) mContext);
holder.svg.addView(item);
}
return convertView;
}
The problem
In the getView() method, each time a view is returned, it removes all the child views in the SVG and instantiates new views called FeaturedItem that are then added to the SVG. Even if the SVG in a particular row, say first row, was populated, when the user scrolls back to it from the bottom, the getView() method will remove all the children views in the SVG and instantiates new views to be populated with.
The inefficiency here is very obvious, and the list view animation skips frames when scrolled quite often.
I can't just reuse the convertView here because it shows the wrong featured items in the StaggeredViewGroup. Therefore I have to remove all children from the StaggeredViewGroup and instantiate and add the views that are relevant to the current position.
The question
Is there a way around this problem? Or are there some alternative approaches to creating a page similar to the Google Play Store featured page, but with each row having different number of featured items thus having its unique height?
There should be an easy way to improve this solution. Just reuse the svg children that are already present, add new ones if they are not enough, and then remove any surplus ones.
For example (in semi-pseudocode, method names may not be exact):
for (int i = 0; i < items.getItemCount(); i++)
{
if (i < svg.getChildCount())
{
FeaturedItem item = i.getChildAt(i);
// This item might've been set to invisible the previous time
// (see below). Ensure it's visible.
item.setVisibility(View.VISIBLE);
// reuse the featuredItem view here, e.g.
item.setItem(items.getItem(i));
}
else
{
// Add one more item
FeaturedItem item = new FeaturedItem(mContext, items.getItem(i));
...
holder.svg.addView(item);
}
}
// hide surplus item views.
for (int i = items.getItemCount(); i < svg.getChildCount(); i++)
svg.getChildAt(i).setVisibility(View.GONE);
/**
as an alternative to this last part, you could delete these surplus views
instead of hiding them -- but it's probably wasteful, since they may need
to be recreated later
while (svg.getChildCount() > items.getItemCount())
svg.removeChildView(svg.getChildCount() - 1);
**/
I have the code below which works except it always hides atleast one real item in the listview because the ad displays at that position.
Example of the problem: I have a list of 4 times, and the adView is displaying at position 3. on the listview I can only see 3 times and the AdView, the 4th item does not get displayed
I played around with increasing the size of the adapter everytime I return an ad but it didn't work very well.
Any ideas?
public View getView(final int position, View row, ViewGroup parent) {
MyHolder holder = null;
boolean showAd = proVersion == false && (position % 8 == k);
if (showAd) {
AdView adView = adList.get(position);
if (adView == null) {
AdView adViewNew = new AdView((Activity) context, AdSize.BANNER, context.getResources().getString(
R.string.adId));
adViewNew.loadAd(Utils.getAdRequest("gps", lat, lng, keywords));
adList.add(position, adViewNew);
return adViewNew;
} else {
return adView;
}
} else if (row == null || row instanceof AdView) {
LayoutInflater inflater = ((SherlockActivity) context).getLayoutInflater();
row = inflater.inflate(viewResourceId, parent, false);
holder = new MyHolder();
holder.textName = (TextView) row.findViewById(R.id.name);
row.setTag(holder);
} else {
holder = (MyHolder) row.getTag();
}
holder.textName.setText(items.get(position).getName());
// more code
return row;
}
#Override
public int getCount() {
if (items != null) {
return items.size();
} else {
return 0;
}
}
There is more than one way of achieving this. The best way is relying upon the ability of the listView to recycle multiple item types.
In your adapter,
getViewTypeCount() - returns information how many types of rows you have in the listView
getItemViewType(int position) returns information on which layout type you should use based on the position. That's where your logic of determining the listView objects should go.
There is a good tutorial on how to use different item types in a list here:
Specifically, look at "Different list items’ layouts".
After that, you need minor arithmetic conversions for your indices so that you map the position to the right place in your data structure (or, you can merge your data structure to include both ad data and item data).
Basically, instead of using items.size() you need to use items.size() + Math.floor(items.size()/NTH_ITEM) and when you get the position, if it's an ad position (position % NTH_ITEM == 0) use a simple conversion like Math.floor(position/NTH_ITEM) to extract from the ad data structure and in similar fashion for your item's structure.
You should rely on the holder pattern of the ListView to reuse your different item view types, like in the tutorial above.
Different approaches, just for the notion, include using something like item wrappers that merge the data source into one and enables differentiating between items by using a type property, like an enum, or a "merging adapter" that merges two specific adapters (this might have a better modularity if you are to include these view types in different lists).
Let me know if you need any help implementing specific parts of this in your use case.
Ben Max's answer is cleaner.
It is recommended to use view types when dealing with different types of view.
Here's a good way to do it.
Create a wrapper for your different data types:
private class ItemWrapper {
public static final int TYPE_NORMAL = 0;
public static final int TYPE_AD = 1;
public static final int TYPE_COUNT = 2;
public ListObject item;
public AdObject adItem;
public int type;
public ItemWrapper(ListObject item) {
this.type = TYPE_NORMAL;
this.item = item
}
public ItemWrapper(AdObject adItem) {
this.type = TYPE_AD;
this.adItem = adItem;
}
}
Now come some of the changes to your adapter:
Let's assume you get you initialize your adapter in the constructor
public class MyAdapter extends BaseAdapter{
ArrayList<ItemWrapper> mWrappedItems = new ArrayList<ItemWrapper>();
public void MyAdapter(List<ListObject> items,AdItem adItem){
for(ListObject item:items){
mWrappedItems.add(new ItemWrapper(item));
}
//you can insert your ad item wherever you want!
mWrappedItems.add(2,new ItemWrapper(adItem));
}
}
Then a few more changes to your adapter:
#Override
public ItemWrapper getItem(int position) {
return mWrappedItems == null ? null : mWrappedItems.get(position);
}
#Override
public int getItemViewType(int position) {
return getItem(position).type;
}
#Override
public int getViewTypeCount() {
//tells the adapter how many different view types it will need to recycle
return ItemWrapper.TYPE_COUNT;
}
Now for your getView....
You'll see this is nicer because you can control how your views are inflated
public View getView(final int position, View row, ViewGroup parent) {
final ItemWrapper item = getItem(position);
final int type = item.type;
if (row == null) {
if (type == ItemWrapper.TYPE_NORMAL) {
//inflate your normal view layouts
} else if (type == ItemWrapper.TYPE_AD) {
//inflate your ad layout
}
}
//now you can easily populate your views based on the type
if (type == ItemWrapper.TYPE_NORMAL) {
//get your item data
final ListObject data = item.item;
//populate as you would normally
} else if (type == ItemWrapper.TYPE_AD) {
final AdItem adItem = item.adItem;
//populate your ad as needed
}
return row;
}
You'll notice that wrapping your data like this gives you great control on how/where your item is display by just manipulating the list instead of doing any weird logic inside your getView(); it also makes it simple to let you inflate layouts according to type and populate them easily.
I hope this helps!
P.S i wrote a blog entry about this topic before as well:
http://qtcstation.com/2012/04/a-limitation-with-the-viewholder-pattern-on-listview-adapters/
I'd go in a different way, instead of adding the ad layout dynamically. Assuming your layout (which is accessed via the View row parameter of your getView() method) is customized, simply add a LinearLayout in it which will be used for the AdView.
As you don't want it to be visible on all the rows, mark its visibility to gone by default, but defining it that way will allow you to control your layout and define it how it should be shown.
Assuming this is your layout file:
<LinearLayout
...>
<!-- Put here the AdView layout -->
<LinearLayout
android:id="#+id/adViewLL"
...
android:visibility="gone" />
<!-- Add whatever else you need in your default layout -->
...
</LinearLayout>
Now simply change your getView() method to set the visibility to VISIBLE whenever you need and put there the View for your AdView.
public View getView(final int position, View row, ViewGroup parent) {
MyHolder holder = null;
boolean showAd = proVersion == false && (position % 8 == k);
if (row == null) {
LayoutInflater inflater = ((SherlockActivity) context).getLayoutInflater();
row = inflater.inflate(viewResourceId, parent, false);
holder = new MyHolder();
holder.textName = (TextView) row.findViewById(R.id.name);
row.setTag(holder);
if (showAd) {
LinearLayout myAd = (LinearLayout) row.findViewByid(R.id.adViewLL);
myAd.setVisibility(View.VISIBLE);
AdView adViewNew = new AdView((Activity) context, AdSize.BANNER, context.getResources().getString(R.string.adId));
...
myAd.addView(adViewNew);
}
} else {
holder = (MyHolder) row.getTag();
}
holder.textName.setText(items.get(position).getName());
// more code
return row;
}
I would add to nKn's answer my following experience:
Encapsulate the Ad view with a ViewSwitcher
Show a message where the Ad goes, something like: "a message from our lovely sponsors…"
When the Ad callback hits you (onAdLoaded), do a holder.switcher.setDisplayedChild(1); // if 1 is where your AdView is.
Have a Flag where you can disable Ads so always do if (showAd && adsEnabled) {}, you never know when you might need to disable ads, if you have an API try to retrieve that value from there.
#4 can also be used for an else clause: else {holder.switcher.setDisplayedChild(0);//your generic message}
Have an "AdController" where you determine how often (every what ## of items you want to insert an ad). and have your list ask if (YourAdController.isAd(position) && adsEnabled) { do the add thing }
This is slightly a logical error.
Here's an explanation on whats happening.
Going by your description, every 8x position is an ad
Why go on to mess with views or maintaining multiple layouts
In your dataset(i.e the list that you are passing to the adapter) just add an item that indicates its an ad item at every 8x position
So, if its a pro version there's no ad item entry in the list
Finally, in your getview
if(ad item)
populate adView
else
populate regularItemView
Hope it simplifies
I have the same problem on one of the applications that i worked on. here's the approach that worked with me.
First,you need to create a class that contains your ad details.
public class AdUnitDetails {
private String mUnitId;
private int mIndex;
public AdUnitDetails(String mUnitId,int mIndex) {
super();
this.mUnitId = mUnitId;
this.mIndex = mIndex;
}
public String getUnitId() {
return mUnitId;
}
public void setUnitId(String mUnitId) {
this.mUnitId = mUnitId;
}
public int getIndex() {
return mIndex;
}
public void setIndex(int mIndex) {
this.mIndex = mIndex;
}
}
Then, you create a Sparsearray containing adviews with their positions
SparseArray<AdView> mAdViewsMap = new SparseArray<AdView>();
Then you need to modify your adapter to receive an arraylist of objects and then you add the ads in their corresponding positions when you fill this list. example(item, item , item , addetails, item, item, etc).
Then, in your adapter. add the following methods:
private static final int TYPE_ITEM= 0;
private static final int TYPE_AD = 1;
private static final int TYPES_COUNT = TYPE_AD + 1;
#Override
public int getViewTypeCount() {
return TYPES_COUNT;
}
#Override
public int getItemViewType(int position) {
if (mItems.get(position) instanceof YourItemClass)
return TYPE_ITEM;
else if (mItems.get(position) instanceof AdUnitDetails)
return TYPE_AD;
return super.getItemViewType(position);
}
Then, in your getView() method,add the following
View view = convertView;
if (mItems.get(position) instanceof YourItemClass) {
// Handle your listview item view
} else if (mItems.get(position) instanceof AdUnitDetails) {
return getAdView(position);
}
return view;
your getAdView() method
private AdView getAdView(final int position) {
if (mAdViewsMap.get(position) == null) {
AdSize adSize = getAdSize(AdSize.BANNER);
AdView mAdView = new AdView(mContext, adSize, ((AdUnitDetails) mItems.get(position)).getUnitId());
mAdView.loadAd(new AdRequest());
mAdViewsMap.put(position, mAdView);
return mAdView;
} else {
return mAdViewsMap.get(position);
}
}
It might be too late but here is how i solved mine.
Converted the current position from int to string ( optional)
and then every time view is inflated check if position contains (3 || 6 || 9) for that every 3rd visible layout shows an advertisement banner. hope it was helpful will post code if needed :)
oh and || means "or" if anyone is wondering
here is the code Ashish Kumar Gupta
RelativeLayout rl = (RelativeLayout) convertView.findViewById(R.id.adviewlayout); //putting the advertisement banner inside a relativelayout/linearlayout if you want. so that you can make it visibile whenever you want.
String pos = String.valueOf(position); // converting listview item position from int to string//
if (pos.endsWith("3")||pos.endsWith("6")||pos.endsWith("9")) {
// every fourth position will show an advertisement banner inside a listview. here is the proof (0,1,2,advertisement,4,5,advertisement,7,8,advertisement,10,11,12,advertisement,14,15,advertisement,17,18,advertisement,20). did not put advertisements in 0 position cuz its rude to put advertisements in like the first layout in my POV//
rl.setVisibility(View.VISIBLE);
}
else
{
rl.setVisibility(View.GONE);
//if its not a 3rd position advertisements will not be shown//
}
you are welcome :)
In arrayAdptor we use following code:
final LayoutInflater inflater = activity.getLayoutInflater();
row = (LinearLayoutCustom) inflater.inflate(R.layout.row, null);
final TextView label = (TextView) row.findViewById(R.id.title);
label.setText(position + "" + items[position]);
return row;
Now suppose some value are null (for example at position 2 , items[2] = null ) so i dont want to show it in row. i want to hide it. if i use
row.setVisibility(View.GONE)
it leaves a blank space at this row which i dont want. so what should i do?
AFAIK you can't return a null view from getView, but you could just make the view invisible and height 1. Although manipulating using the getCount is probably the preferred way.
view.setVisibility(View.INVISIBLE);
view.getLayoutParams().height = 1;
You'll need to have the adapter return the total number of non-null items with getCount and then keep a mapping of position to your internal data structure.
For example.
You have a list
1 - John
2 - null
3 - Bill
4 - Susan
5 - null
When getCount is called it returns 3.
Then when getView is called on position 1 you return the item at list[1].
getView on position 2 returns list[3] (as it's the 2nd non-null),
and so forth.
This is the only way I've found to do this.
You can use a View that has no height for the "hidden" items so that you don't have to do all the model housekeeping and mapping. For example, suppose you had a "filter" EditText field that when data is entered it only keeps matching items:
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) MyActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout view = (RelativeLayout) inflater.inflate(R.id.myListLayout, null, false);
...
// if we didn't match filter be GONE and leave
if (filterText.length() > 0 && myModelValueAtPosition.toLowerCase().indexOf(filterText) < 0){
view = (RelativeLayout) inflater.inflate(R.layout.myListLayoutWithZeroHeight, null, false);
view.setVisibility(View.GONE); // this doesn't really do anything useful; I'd hoped it would work by itself, but turns out the zero height layout is the key
return view;
}
view.setVisibility(View.VISIBLE);
...
}
Here you need to write the logic in your getCount(),getItemId() and getItem(),
It will create the no of rows what the getCount return
//How many items are in the data set represented by this Adapter
public int getCount() {
return //Should return the count of rows you need to display (here the count excluding null values)
}
And
//This need to return data item associated with the specified position in the data set.
public Object getItem(int position) {
return //Return the object need to display at position, need the logic to skip null value
}
Edit:So in your getview
public View getView(int position, View convertView, ViewGroup parent) {
----
getItem(position);//Object corresponding to position ,In your case it will not be null since you need to write the logic to skip null object at getItem
----
}
This is the solution I implemented, here is a code example for everyone that is looking it:
class Shipment_Adapter extends ArrayAdapter<Shipment>{
....
ArrayList<Integer> emptyPositions = new ArrayList<>();
public Shipment_Adapter(Context context, int shipment_row, Shipment[] myShipments){
super(context, R.layout.shipment_row,myShipments);
//populate emptyPositions list
for(int i = 0; i< myShipments.length; i++){
if(myShipments[i]==null){
emptyPositions.add(i);
}
}
this.mShipment = myShipments;
this.mContext = context;
}
//corrects size of List
#Override
public int getCount() {
return (mShipment.length - emptyPositions.size());
}
//recursive function that checks if position is not empty until it isn't
public int isEmpty(int positiontocheck){
int newposition;
if(emptyPositions.contains(positiontocheck)){
//true? check that next one is free
return isEmpty(positiontocheck+1);
}else{
newposition = positiontocheck;
}
return newposition;
}
}
public View getView(int position, View convertView, ViewGroup parent) {
//now just need to use isEmpty to get the next not empty position in
//case our real position is empty
position= isEmpty(position);
Shipment shipment = mShipment[position];
...//and so on
}
hope this helps!