Getviewtypecount and Getitemviewtype Methods of Arrayadapter

getViewTypeCount and getItemViewType methods of ArrayAdapter

These handle the case where you want different types of view for different rows. For instance, in a contacts application you may want even rows to have pictures on the left side and odd rows to have pictures on the right. In that case, you would use:

@Override
public int getViewTypeCount() {
return 2;
}

@Override
public int getItemViewType(int position) {
return position % 2;
}

The framework uses your view type to decide which views to hand you via convertView in your getView method. In other words, in the above example, your even rows will only get recycled views with pictures on the left side to reuse, and odd rows will only get ones with pictures on the right.

If every row in your list has the same layout, you don't need to worry about view types. In fact, BaseAdapter.java provides a default behavior for all adapters:

public int getItemViewType(int position) {
return 0;
}

public int getViewTypeCount() {
return 1;
}

This indeed provides you with the same view type for every row.

Edit - to outline the general flow:

  1. You bind data to your AdapterView using an adapter.
  2. The AdapterView tries to display items that are visible to the user.
  3. The framework calls getItemViewType for row n, the row it is about to display.
  4. The framework checks its recycled views pool for views of row n's type. It doesn't find any because no views have been recycled yet.
  5. getView is called for row n.
  6. You call getItemViewType for row n to determine what type of view you should use.
  7. You use an if/switch statement to inflate a different xml file depending on which view type is required.
  8. You fill the view with information.
  9. You return the view, exiting getView, and your row's view is displayed to the user.

Now, when a view is recycled by scrolling off the screen it goes into a recycled views pool that is managed by the framework. These are essentially organized by view type so that a view of the correct type is given to you in convertView parameter in your getView method:

  1. The framework again calls getItemViewType for the row it wants to display.
  2. This time, there is a view in the recycled pool of the appropriate type.
  3. The recycled view is passed to you as the convertView parameter to your getView method.
  4. You fill the recycled view with new information and return it.

Why does the Android View API care about an ArrayAdapter's getViewTypeCount?

I've just reviewed the Android source to see exactly where getViewTypeCount is used. It's in the abstract AbsListView class' inner RecycleBin class' setViewTypeCount method.

This setViewTypeCount method is used to specify the initial size of the ArrayList<View> of scrapViews. This ArrayList itself is an ArrayList of ArrayList<View>s -- one for each "view type count."

Furthermore the value returned by getViewTypeCount is assigned to RecycleBin's member variable mViewTypeCount. This member is used to control the logic for recycling for cases where there is only one vs. many views throughout a handful of methods in the RecycleBin class(as everyone has been alluding to).

So the answer is, I believe, that getViewTypeCount is used by RecycleBin so that it knows whether it has just one or numerous Views to handle. So to speak.

(Thanks everyone for opening my eyes to recycling and motivating me to read the source.)

Why do recipes promote overriding getItemViewType and getViewTypeCount when it doesn't seem necessary?

why should one override those two methods if one does not have to?

Add a few dozen restaurants, all of differing types, and watch as your row recycling goes haywire when you scroll, given your implementation shown above.

getItemViewType() and getViewTypeCount() are to ensure that row recycling works. Android will maintain separate object pools and will only hand you a row back to recycle that is of the correct type.

In your solution, you could inflate a R.layout.row_delivery row, then later get it handed back to you for recycling when you are really in need of a R.layout.row_sit_down row.

BTW, do not use inflate(R.layout.row_take_out, null) in an AdapterView. To get RelativeLayout rules to process correctly, use inflate(R.layout.row_take_out, parent, false).

getPositionForSection and getSectionForPosition and getSections methods for ArrayAdapter

Charsequence alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

Here

A = 0
B = 1
C = 2
D = 3
E = 4
F = 5
.
.
.
Z = 25

Create a new AlphabetIndexer

AlphabetIndexer alphabetIndexer = new AlphabetIndexer(cursor, sortedColumnIndex, alphabets);

getSections() retreives the String[] of the alphabets that is provided to the alphabetIndexer

String[] sections = (String[]) alphabetIndexer.getSections();

alphabetIndexer.getSectionForPosition(position) retreives the section for the current position

Consider the given case

position  Data             getSectionForPosition(position)
________ _________ __________

0 Ajdhfj j 0
1 Aadf hdsf 0
2 Ajfkldsahf 0
3 Asdhfa df 0
4 Badhf 1
5 Bdfj sadif 1
6 Bghoi ij 1
7 Bjkojg o 1
8 Cadj fkljsdf 2
9 Cgjds kfja 2
10 Cn khdfaj 2
11 Cph iohsdf 2
12 Czjfa sh 2
13 Dfgoa hjoifaj 3
14 Dzfjdak sfh 3
15 Fhf adhf 5
16 Zdfh ajdf 25

To get the section for the position

String section = sections[getSectionForPosition(position)];

alphabetIndexer.getPositionForSection(section) retreives the first position at which the data starts with that section

section      getPositionForSection(section)
_________ ____________________
0 0
1 4
2 8
3 13
4 13
5 15
6 15
7 15
8 15
. .
. .
. .
23 15
24 15
25 16

Hope this helps you.

An example of using alphabetIndexer

public abstract class SectionedListAdapter extends ResourceCursorAdapter {

private SparseIntArray sections;
private SparseIntArray cursorPositions;
private Context mContext;
private int sortedColumnIndex;

private static final int NORMAL_LIST_VIEW = 0;
private static final int SECTION_LIST_VIEW = 1;
private static final int NULL_LIST_VIEW = 2;

public SectionedListAdapter(Context context, int layout, Cursor c,
boolean autoRequery, int sortedColumnIndex, int defaultBitmapResId) {
super(context, layout, c, autoRequery);
this.mContext = context;
this.sortedColumnIndex = sortedColumnIndex;
setSortedCursorColumn(c);
}

public void setSortedCursorColumn(Cursor cursor){
if (cursor == null){
return;
}
AlphabetIndexer alphabetIndexer = new AlphabetIndexer(cursor, sortedColumnIndex, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
sections = new SparseIntArray();
int m=0;
int t = 'A';
for (int i=0; i < 26; i++){
if ((i+1) < 26) {
int position = alphabetIndexer.getPositionForSection(i);
int temp = alphabetIndexer.getPositionForSection(i + 1);
if (temp != position){
sections.put(position + m, t+i);
m++;
}
} else {
int position = alphabetIndexer.getPositionForSection(i);
int temp = alphabetIndexer.getPositionForSection(i-1);
if (position != temp){
sections.put(position + m, t+i);
}
}
}
int temp = 0;
cursorPositions = new SparseIntArray();
for (int i=0; i<cursor.getCount() + sections.size(); i++){
if (sections.get(i, -1) != -1){
temp ++;
cursorPositions.put(i, -1);
} else {
cursorPositions.put(i, i - temp);
}
}
}

@Override
public View getView(int position, View view, ViewGroup parent) {
if (getItemViewType(position) == NORMAL_LIST_VIEW){
if (view == null){
view = newView(mContext, getCursor(), parent);
}
getCursor().moveToPosition(cursorPositions.get(position));
bindView(view, mContext, getCursor(), position);
} else if (getItemViewType(position) == SECTION_LIST_VIEW) {
if (view == null){
view = newSectionView(mContext, getCursor(), parent);
}
bindSectionView(view, mContext, (char)sections.get(position));
} else {
if (view == null){
} else {
view = new ViewStub(mContext);
}
}
return view;
}

public abstract View newSectionView(Context context, Cursor cursor, ViewGroup parent);

public abstract void bindSectionView(View view, Context context, char text);

public abstract void bindView(View view, Context context, Cursor cursor, int position);

@Override
public boolean areAllItemsEnabled() {
return false;
}

@Override
public boolean isEnabled(int position) {
return cursorPositions.get(position) >= 0;
}

@Override
public int getItemViewType(int position) {
if (cursorPositions.get(position) >= 0){
return NORMAL_LIST_VIEW;
}else if (cursorPositions.get(position) == -1){
return SECTION_LIST_VIEW;
} else {
return NULL_LIST_VIEW;
}
}

@Override
public int getViewTypeCount() {
return 3;
}

private void putCursorExclusion(int position, boolean isActualPosition){
if (isActualPosition) {
cursorPositions.put(position, -cursorPositions.get(position));
} else {
int actualPosition = cursorPositions.keyAt(cursorPositions.indexOfValue(position));
cursorPositions.put(actualPosition, -position);
}
}

private void removeCursorExclusion(int position, boolean isActualPosition){
if (isActualPosition){
cursorPositions.put(position, -cursorPositions.get(position));
} else {
int actualPosition = cursorPositions.keyAt(cursorPositions.indexOfValue(-position));
cursorPositions.put(actualPosition, position);
}
}

@Override
public int getCount() {
return cursorPositions == null ? 0 : cursorPositions.size();
}

public SparseIntArray getSections() {
return sections;
}

public SparseIntArray getCursorPositions() {
return cursorPositions;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
//do nothing here
}

@Override
public Cursor swapCursor(Cursor newCursor) {
return super.swapCursor(newCursor);
}

@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
}

@Override
public void changeCursor(Cursor cursor) {
setSortedCursorColumn(cursor);
super.changeCursor(cursor);
}
}

To use this just create a new class extends SectionedListAdapter and then provide required details.

When getItemCount and getItemViewType method get called in Recycler Adapter

Q1)
The getViewType() method will be called before the onCreateViewHolder() method each time your custom view is created.

You need to create a list with your custom list items List<CustomItem> list=method_to_return_your_list() and each of them with a getViewType() getter method.

In your get getItemViewType()

public int getItemViewType(int position) {
return list.get(position).getViewType();
/*this returns the view type of each element in the list*/
}

This can be either 0 or 1 considering your switch case statement in the onCreateViewHolder() method

Q2)
The getItemCount() method should return the number of list items.

public int getItemCount() {
return list.size();
}

Q3)
Do not create another recyclerview adapter for the same recyclerview

Also I fogot.
Instead of creating new ViewHolders ,just change the itemView in the view holder like

@Override 
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder(itemView0);
case 2: return new ViewHolder(itemView1);
...
}
}

Why getViewTypeCount() return different values?

TL;DR As a general rule, for every if you have in your getView(...), make sure there is an else that handles the negative case or you will see old data in your views.


It seems you are misunderstanding the role getItemViewType(int position) and getViewTypeCount() play. Here is a good description of why and how to use them.

In your case, since you only have one layout, implementing either method should not be necessary. If you are seeing recycled data in your views, then there is some bug in your getView(...) method. Make sure you handle all cases when populating a view with data.

For example, say you have a list of items and you want to display a red background if the position is even and blue if it is odd. Lets also assume that by default your view has a red background. You might implement your getView(...) like so:

@Override
public View getView(final int position, final View convertView, ViewGroup parent) {
if(convertView == null) {
//Inflate your view here
}
if(position % 2 != 1) {
//Only need to change background if we're odd, since it's red by default
convertView.setBackgroundColor(Color.BLUE);
}
}

At first glance, this seems like a good implementation. When you add in view recycling, however, you will see that some of your even views may end up with a blue background. The reason for this is because you don't explicitly set the background for even views so they will remain the color they were set to in the previous row they were used in. A better implementation would be:

@Override
public View getView(final int position, final View convertView, ViewGroup parent) {
if(convertView == null) {
//Inflate your view here
}
if(position % 2 != 1) {
convertView.setBackgroundColor(Color.BLUE);
} else {
//Set the background no matter what so we make sure our view reflects our data
convertView.setBackgroundColor(Color.RED);
}
}


Related Topics



Leave a reply



Submit