Listview to recyclerview: how do I implement 2 possible viewholders? - android

In my Listview this code works:
for (int number = 0; number < matchingContacts.size(); number++) {
//if a phone number is in our array of matching contacts
if (matchingContacts.contains(selectPhoneContact.getPhone()))
{
//if a matching contact, no need to show the Invite button
viewHolder.invite.setVisibility(View.GONE);
//once a matching contact is found, no need to keep looping x number of time, move onto next contact
break;
} else {
//if not a matching contact, no need to show the check box
viewHolder.check.setVisibility(View.GONE);
}
}
If a phone number is in the matching arraylist then it should make the invite button invisible, if it is not in the matching arraylist it should make the checkbox invisible.
But not in my recyclerview, in which I am trying to make the code work.
On first load it looks ok but as soon as you start to scroll the views get messed up - checkboxes and buttons appear where they are not supposed to.
I've read that in Recyclerview you are supposed to implement this with case statements, and I've looked here Why RecyclerView items disappear with scrolling and here How to create RecyclerView with multiple view type? but for the life of me I cannot get it to work!
Can you help?
Here is my code:
public class PopulistoContactsAdapter extends RecyclerView.Adapter<PopulistoContactsAdapter.ViewHolder> {
//make a List containing info about SelectPhoneContact objects
public List<SelectPhoneContact> theContactsList;
Context context_type;
ArrayList<String> matchingContacts = new ArrayList<String>();
public static class ViewHolder extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public ViewHolder(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
public PopulistoContactsAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context, int activity) {
theContactsList = selectPhoneContacts;
context_type = context;
matchingContacts.add("+3531234567");
matchingContacts.add("+3536789012");
matchingContacts.add("+3530987654");
matchingContacts.add("+3538765432");
}
#Override
public PopulistoContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View contactView = inflater.inflate(R.layout.recycler_blueprint, parent, false);
ViewHolder viewHolder = new ViewHolder(contactView);
return viewHolder;
}
#Override
public void onBindViewHolder(final PopulistoContactsAdapter.ViewHolder viewHolder, final int position) {
//bind the views into the ViewHolder
//selectPhoneContact is an instance of the SelectPhoneContact class.
//We will assign each row of the recyclerview to contain details of selectPhoneContact:
//The number of rows will match the number of contacts in our contacts list
final SelectPhoneContact selectPhoneContact = theContactsList.get(position);
//a text view for the name, set it to the matching selectPhoneContact
TextView title = viewHolder.title;
title.setText(selectPhoneContact.getName());
//a text view for the number, set it to the matching selectPhoneContact
TextView phone = viewHolder.phone;
phone.setText(selectPhoneContact.getPhone());
Button invite = viewHolder.invite;
CheckBox check = viewHolder.check;
for (int number = 0; number < matchingContacts.size(); number++) {
//if a phone number is in our array of matching contacts
if (matchingContacts.contains(selectPhoneContact.getPhone()))
{
//if a matching contact, no need to show the Invite button
viewHolder.invite.setVisibility(View.GONE);
//once a matching contact is found, no need to keep looping x number of time, move onto next contact
break;
} else {
//if not a matching contact, no need to show the check box
viewHolder.check.setVisibility(View.GONE);
}
}
}
#Override
public int getItemCount() {
return theContactsList.size();
}
}

Method 1: Actually in you case, using multiple View types is not necessary. This might be an easier way. Instead, I would recommend you amend your SelectPhoneContact class to include a simple boolean field (maybe called isMatching) that flags whether or not this phone number is a matching contact. You can then create a simple setter and getter methods like setIsMatchingContact(boolean) and isMatchingContact() to update/read this flag.
When you're initializing the list and adding the SelectPhoneContact, do the pre-processing of determining which instances belong or don't belong in the matching contacts by setting the setIsMatchingContact(boolean) method. In your onBindViewHolder method, rather than iterating over matchingContacts, you just check selectPhoneContact.isMatchingContact() and change the visibility accordingly. This is also more efficient as you don't have to perform a potentially expensive operation of iterating through a large list in a bind method which could cause the scrolling to stutter and have issues.
If your matchingContacts list changes throughout time, you could always write a method that iterates through the SelectPhoneContact list and resets the isMatching boolean.
Method 2: If you rather not extend the your SelectPhoneContact class to have those two methods I mentioned, you could alternatively create a private static wrapper class like this:
private static class SelectPhoneContactItem {
SelectPhoneContact selectPhoneContact;
boolean isMatching;
}
Then use this as the primary list in your adapter:
public List<SelectPhoneContactItem> theContactsList;
Like Method 1, you should the pre-processing of the figuring out which SelectPhoneContactItem is in the matchingContacts and assign the isMatching boolean as needed.
You should probably go with Method 1 unless there are some design constraints. Let me know if my answer makes sense, hope this helps!

RecyclerView can handle multiple view types with different view holders.
First of all you have to override the getItemViewType(int position) method on your adapter which will return type of the object according its position. Then create view holder class for each view type.
Handle onCreateViewHolder(ViewGroup parent, int viewType) method considering the view type:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == YOUR_FIRST_TYPE) {
//inflate first type of view
return new FirstTypeViewHolder(view);
} else if (viewType == YOUR_SECOND_TYPE) {
//inflate second type of view
return new SecondTypeViewHolder(view);
}
}
Handle onBindViewHolder(ViewHolder viewHolder, int position) considering the view type:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int viewType = getItemViewType(position);
if (viewType == YOUR_FIRST_TYPE) {
FirstTypeViewHolder firstTypeViewHolder = (FirstTypeViewHolder) viewHolder;
//do your stuff
} else if (viewType == YOUR_SECOND_TYPE) {
SecondTypeViewHolder secondTypeViewHolder = (SecondTypeViewHolder) viewHolder;
//do your stuff
}
}
You can take a look on this tutorial.

Related

How to insert list item into listview without modifying the underlying data set?

I have a ListView that is populated by an ArrayAdapter, and I need to insert an advertisement after every Nth legitimate list item. The easiest way I can think of to do this would be to modify the array that defines the adapter's data set directly (or indirectly, via ArrayAdapter::add/insert) as follows:
/**
* Injects ad entries into the station list after every N legit entries
*
* #param adapter the arrayadapter that will generate the views representing our legit and ad list items
*/
private void injectAdEntries(MyArrayAdapter adapter){
int legitItemCount = adapter.getCount();
int adStride = 4;
if(legitItemCount >= adStride) {
//adstride-1 to account for the 0 index
for (int legitItemIndex = adStride-1; legitItemIndex < legitItemCount; legitItemIndex += adStride) {
//create clone of last legit entry, to use as context
// data for the ad, and mark it 'illegitimate' (ad)
LegitObject totallyLegit = new LegitObject(adapter.getItem(legitItemIndex));
totallyLegit.setLegit(false);
adapter.insert(totallyLegit,legitItemIndex+1);
}
}
}
After the injection, MyArrayAdapter's getView override can detect the false legit value and handle advertisement entries differently from legitimate entries.
The trouble is that I don't want to pollute the legitimate data array. Ideally, I would insert these advertisement list items without modifying the underlying data set in any way. Is there a way to make an adapter produce views that aren't reflected in its data set?
Edit
I also need all the items in the legitimate data set to be displayed in the ListView; the ad list items need to appear 'in addition to' rather than 'instead of' legitimate list items.
I will assume that you are using a recyclerView as a list. So, to have heterogenous layouts (i.e.: YOUR ACTUAL ITEM LAYOUT + AD LAYOUT) you should follow these steps:
Override getItemView like
#Override
public int getItemViewType(int position) {
// insert an Ad every multiple of 5
if (position % 5 == 0) {
return AD_POSITION;
}
return NORMAL_POSITION;
}
Next, you need to override the onCreateViewHolder method to tell the RecyclerView.Adapter about which RecyclerView.ViewHolder object to create based on the viewType returned.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
RecyclerView.ViewHolder viewHolder;
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
switch (viewType) {
case AD_POSITION:
View v1 = inflater.inflate(R.layout.list_item_ad, viewGroup, false);
viewHolder = new AdViewHolder(v1);
break;
default:
// NORMAL_POSITION
View v = inflater.inflate(R.layout.list_item, viewGroup, false);
viewHolder = new NormalViewHolder(v);
break;
}
return viewHolder;
}
For more details you can check this link out.

Android Chat Application : Message Adapter does not update the items in list view correctly

This is my first question in Stack Overflow, I have been building a simple chat application on Android which loads the chat History from a online database and it will be displayed in a list view using a customised message adapter.
Here is the current state of the program:
Demo
However, the layout of the list items is not correct after the 6th chat message down the array list, and all the following chat messages are repeating the layout of the first 6 messages.
Here is the code for my adapter:
public class messageAdapter extends ArrayAdapter<chatMessage> {
private Activity activity;
private List<chatMessage> messages;
public messageAdapter(Activity context, int resource, List<chatMessage> objects) {
super(context, resource, objects);
this.activity = context;
this.messages = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
int layoutResource = 0; // determined by view type
chatMessage chatMessage = getItem(position);
int viewType = getItemViewType(position);
if (chatMessage.isMine()) {
layoutResource = R.layout.chat_bubble_right;
} else {
layoutResource = R.layout.chat_bubble_left;
}
if (convertView != null) {
holder = (ViewHolder) convertView.getTag();
} else {
convertView = inflater.inflate(layoutResource, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
Log.d("ViewID", "generated");
}
//set message content
holder.message.setText(chatMessage.getContent());
return convertView;
}
#Override
public int getViewTypeCount() {
// return the total number of view types. this value should never change
// at runtime
return 2;
}
#Override
public int getItemViewType(int position) {
// return a value between 0 and (getViewTypeCount - 1)
return position % 2;
}
private class ViewHolder {
private TextView message;
public ViewHolder(View v) {
message = (TextView) v.findViewById(R.id.txt_msg);
}
}
And this is the method that I load chat messages into the Array List:
private class getChatHistory extends AsyncTask<DBConnection,Long,JSONArray> {
#Override
protected JSONArray doInBackground(DBConnection... params) {
return params[0].getChatHistory(userID);
}
#Override
protected void onPostExecute(JSONArray jsonArray) {
chatData = jsonArray;
if (chatData != null)
{
for (int i = 0; i < chatData.length(); i++)
{
JSONObject currentItem = null;
try
{
currentItem = chatData.getJSONObject(i);
int msgID = currentItem.getInt("MessageID");
String currentText = currentItem.getString("MessageContent");
int senderID = currentItem.getInt("SenderID");
int receiverID = currentItem.getInt("ReceiverID");
chatMessage currentMessage = new chatMessage(currentText, senderID, userID);
Log.d("Is Mine", Boolean.toString(currentMessage.isMine()));
messageHistory.add(currentMessage);
DBAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
}
And here is the JSONArray I obtained from the PHP file that I ran:
[{"MessageID":"1","SenderID":"1","ReceiverID":"8","Duration":"2.4","MessageContent":"agnblean liajiaj vliwv fla","MessageLength":"26","Status":"received","Date":"2016-04-04 14:00:00"},
{"MessageID":"2","SenderID":"8","ReceiverID":"1","Duration":"3.1","MessageContent":"akwuehrgeubwfcofawve","MessageLength":"20","Status":"received","Date":"2016-04-04 17:00:00"},
{"MessageID":"3","SenderID":"8","ReceiverID":"1","Duration":"3.1","MessageContent":"akwuehrgeubwfjurawve","MessageLength":"20","Status":"received","Date":"2016-04-04 17:00:05"},
{"MessageID":"4","SenderID":"8","ReceiverID":"1","Duration":"3.1","MessageContent":"akwuehrgeubwalwrawve","MessageLength":"20","Status":"received","Date":"2016-04-04 17:00:10"},
{"MessageID":"5","SenderID":"1","ReceiverID":"8","Duration":"3.1","MessageContent":"akwuehrgeubwalwrawve","MessageLength":"20","Status":"received","Date":"2016-04-04 17:01:10"},
{"MessageID":"8","SenderID":"1","ReceiverID":"8","Duration":"4.6","MessageContent":"vsjkgkgredjegwhkaga","MessageLength":"23","Status":"received","Date":"2016-04-05 05:00:00"},
{"MessageID":"9","SenderID":"8","ReceiverID":"1","Duration":"5.2","MessageContent":"agrlanwligna","MessageLength":"21","Status":"received","Date":"2016-04-06 00:00:00"},
{"MessageID":"10","SenderID":"8","ReceiverID":"1","Duration":"7.2","MessageContent":"akewgaughurawaarg","MessageLength":"12","Status":"received","Date":"2016-04-12 00:00:00"},
{"MessageID":"11","SenderID":"1","ReceiverID":"8","Duration":"7.2","MessageContent":"wgkakjrgnjange","MessageLength":"41","Status":"received","Date":"2016-04-15 00:00:00"},
{"MessageID":"12","SenderID":"1","ReceiverID":"8","Duration":"4.67","MessageContent":"yikes","MessageLength":"5","Status":"received","Date":"2016-04-21 00:00:00"},
{"MessageID":"13","SenderID":"8","ReceiverID":"1","Duration":"8.2","MessageContent":"iobanoine","MessageLength":"4","Status":"received","Date":"2016-04-30 00:00:00"}]
So I thought that this would produce the correct layout for the chat history, which the active user being user ID = 1, and all the message with sender ID = 1 should be on the right hand side of the list view, but instead I got this:
screenshot
This is the screenshot of the 5-8th element in the list view, but the 7th element is on the right hand side instead of being on the left hand side, and the later element keep on repeating the previous 6 element's pattern. I have checked the log for the convert view and it only shows up 6 times, is that in anyway related to this error? And how do I solve this problem of the adapter not locating the list item resource correctly?
EDIT : I have changed the override of the getItemViewType() into this
#Override
public int getItemViewType(int position) {
chatMessage chatMessage = getItem(position);
if (chatMessage.isMine()) {
return 0;
} else {
return 1;
}
}
And I have also removed the override method for getViewTypeCount, and changed the condition a bit in the getView() method:
int viewType = getItemViewType(position);
if (viewType==0) {
layoutResource = R.layout.chat_bubble_right;
} else {
layoutResource = R.layout.chat_bubble_left;
}
Now the chat message list is in normal order up to the 8th element, and then the order become incorrect again.
EDIT 2
I have trace the log for the number of list item generated (i.e. new items) and this is the result I get after scrolling down to the bottom of the list view:
04-06 19:23:54.894 11202-11202/com.example.user.normalinterface D/ViewID: generated
04-06 19:23:54.907 11202-11202/com.example.user.normalinterface D/ViewID: generated
04-06 19:23:54.912 11202-11202/com.example.user.normalinterface D/ViewID: generated
04-06 19:23:54.914 11202-11202/com.example.user.normalinterface D/ViewID: generated
04-06 19:23:56.850 11202-11202/com.example.user.normalinterface D/ViewID: generated
is this in anyway related to my problem? It seeems that all the subsequent record in the list view are repeating the pattern from the first 5 item in the list.
I think it come from the getViewTypeCount() method. Here you say that you have 2 different types of layout. And with the method getItemViewType() you say that the different types are one out of 2 messages. But you don't event use the type in the getView method.
Apparently you don't need those 2 types, so I would recommend that you just remove the 2 methods :
getViewTypeCount() and
getItemViewType()
I don't think you need them in you case.
EDIT :
You actually need those method, but you overrided getItemViewType badly.
#Override
public int getItemViewType(int position) {
chatMessage chatMessage = getItem(position);
if (chatMessage.isMine()) {
return 0;
} else {
return 1;
}
}
In my experience, but may not be relevant to this, you need to call notifyDataSetChanged() in the adapter, so set up a method called refreshData in the adapter like this:
public void refreshData(ArrayList<yourList> data) {
mValues.clear();
mValues.addAll(data);
notifyDataSetChanged();
}
and call from your activity:
mAdapter.refreshData(newData)
with the new data as an argument.
Also, make sure you reference the adapter class properly, so try adding to the beginning of your class:
private final DBAdapter mAdapter;
Then in getChaHistory add:
mAdapter = DBAdapter
The idea being that you have a correctly referenced handle to your adapter. Note the change to previous answer where you now call mAdapter.refreshData(newData) instead of DBAdapter.refreshData(newdata). Really I think you should pass the adapter to the class, which is what I do, but you can try this way and see how you get on?
This will work correctly, but won't be as fast. It decides which layout to instantiate depending on the value if "isMine()" If "isMine() is true then the layout which contains the right aligned TextView is instantiated. Otherwise, the other layout is instantiated, (where the TextView is left aligned).
#Override
public View getView(int position, View convertView, ViewGroup parent){
if(messages.get(position).isMine()){
item = ((Activity)context).getLayoutInflater().inflate(R.layout.chat_bubble_right,null);
}
else{
item = ((Activity)context).getLayoutInflater().inflate(R.layout.chat_bubble_left,null);
}
// set the text in the view.
((TextView) item.findViewById(R.id.chat_text)).setText(data.get(position).getMessage());
return item;
}
The chat_text id refers to the TextView within the instantiated layout. (Both layouts contain a TextView with the id chat_text).
I used this Google IO event as a resource:
https://www.youtube.com/watch?v=wDBM6wVEO70

How to add a dynamic view in between items of RecyclerView in android?

I need to add a small strip in between items of a RecyclerView. This strip can come after different number of items in a list. This needs to be done dynamically.
I need to implement something like what FitBit has done:
I also need the first row i.e. the one saying "This Week" to stick on top even if the page scrolls down.
You should use the concept of different view types using getItemViewType(int). Then on onCreateViewHolder(ViewGroup, int) you can check which type you should inflate/create.
Example:
#Override
public int getItemViewType(int position) {
// you should return the view type, based on your own dynamic logic
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
// handle each view type accordingly
}
}
Use StickyHeaderRecyclerView library
It is very easy to use
You can use the concept of multiple view types in your RecyclerView, Just by using getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
For example you can use below model:
public class Data{
int field1;
float filed2;
int rowType // 1,2,2,...N this will fill by you whenever you will
//creating arraylist for your recyclerview
}
public class Custome Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<Data> mItems;
class ViewHolderRowType1 extends RecyclerView.ViewHolder {
...
}
class ViewHolderRowType2 extends RecyclerView.ViewHolder {
...
}
....
class ViewHolderRowTypeN extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
return mItems.get(position).rowType;
//or
//return positon%2; // This will based on your condition
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolderRowType0(...);
case 1: return new ViewHolderRowType1(...);
...
case N: return new ViewHolderRowTypeN(...);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
//Just check which view type is going to bind and then fill the data accordingly in your rows
if(vh instanceof ViewHolderRowType1){
// Fill the data for first view type
} else if (vh instanceof ViewHolderRowType2) {
// Fill the data for second view type
} else if (vh instanceof ViewHolderRowTypeN){
// Fill the data for Nth view type
}
}
For your sticky "this view weak", you can add it at top of your RecyclerView and then handle it by scroll Event of RecyclerView
There are two ways to implement such RecyclerView
Add header in every layout and hide/show based on your requirement(preferable).
Or use two different layouts for header and content(not preferable because it can cause problem in total count of items in adapter).
In your custom POJO / GetterSetter class, add one field for headerStatus(either boolean or int), to identify whether to show header or not.
Now in adapter override public int getItemViewType(int position).
static final int TYPE_ITEM = 0;
static final int TYPE_SEPARATOR = 1;
#Override
public int getItemViewType(int position) {
if (mData.get(position).getHeaderStatus() == 0)
return TYPE_ITEM;
else
return TYPE_SEPARATOR;
}
Now at the time of inflating the layout in getView() you can check the row type by
int rowType = getItemViewType(position);
For case 1, you need to visible the header and set appropriate data in it.
For case 2, you need to inflate that header layout and add appropriate data in it.
If you want to do it in "proper" way, without hacks, you should write your own LayoutManager, and handle those cases by hands. It is not as hard as it sounds, but will take some efforts.

Repeating of rows on Scroll in GridView

I am having a gridView and the data in the gridView is coming from server.
Now I am having some views in it that will show for some rows and will not show for some rows depends on the sever conditions.
Ex : I am having a LinearLayout which is having an imageView and 2 TextViews, this layout will be visible only for some rows based on server data.
First time it is coming fine but as I scroll down/up, the view of the rows get change.
For Ex: Like in the first row If I am not having this LinearLayout and in 2nd or 3rd row this layout is visible, the when I scroll down and then again scroll up, the first row also get that Layout exact same as the last scrolled position.
I am using Holder pattern, can you please help me here, I am stuck here.
Thank you so much in advanced.
The views are stateless so if you show the linearlayout on someviews you need to remember to hide it for the others.
onBindViewHolder will not give you a fresh view from xml but the view you mutated. Basically just remember to set the LinearLayout back to gone.
A better way would be to use multiple xml files and implement getItemViewType showing and hiding views can cause the scroll to gitter, although if heights remain the same you might get away with it.
public class ExampleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<ContactsContract.Data> data;
private static final int TYPE_A = 0;
private static final int TYPE_B = 1;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if(viewType == TYPE_A) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_a, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_b, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.setData(data.get(position);
}
#Override
public int getItemViewType(int position) {
if(data.get(position).youCondition()) {
return TYPE_A;
} else {
return TYPE_B;
}
}
#Override
public int getItemCount() {
return data.size();
}
}
This is a basic example of how it could be done. Will need to implement your own ViewHolders i'd suggest making a different one for each view type from a base class that has the set data method.

How can display an AdView at every n-th position in a listView

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 :)

Categories

Resources