Merge branch 'develop' into imageGrid
[pub/Android/ownCloud.git] / src / third_parties / in / srain / cube / GridViewWithHeaderAndFooter.java
1
2 /*
3 * Copyright (C) 2013 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package third_parties.in.srain.cube;
18
19
20 import android.annotation.TargetApi;
21 import android.content.Context;
22 import android.database.DataSetObservable;
23 import android.database.DataSetObserver;
24 import android.os.Build;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.*;
30
31 import java.lang.reflect.Field;
32 import java.util.ArrayList;
33
34 /**
35 * A {@link GridView} that supports adding header rows in a
36 * very similar way to {@link android.widget.ListView}.
37 * See {@link GridViewWithHeaderAndFooter#addHeaderView(View, Object, boolean)}
38 * See {@link GridViewWithHeaderAndFooter#addFooterView(View, Object, boolean)}
39 */
40 public class GridViewWithHeaderAndFooter extends GridView {
41
42 public static boolean DEBUG = false;
43
44 /**
45 * A class that represents a fixed view in a list, for example a header at the top
46 * or a footer at the bottom.
47 */
48 private static class FixedViewInfo {
49 /**
50 * The view to add to the grid
51 */
52 public View view;
53 public ViewGroup viewContainer;
54 /**
55 * The data backing the view. This is returned from {@link ListAdapter#getItem(int)}.
56 */
57 public Object data;
58 /**
59 * <code>true</code> if the fixed view should be selectable in the grid
60 */
61 public boolean isSelectable;
62 }
63
64 private int mNumColumns = AUTO_FIT;
65 private View mViewForMeasureRowHeight = null;
66 private int mRowHeight = -1;
67 private static final String LOG_TAG = "grid-view-with-header-and-footer";
68
69 private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();
70 private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>();
71
72 private void initHeaderGridView() {
73 }
74
75 public GridViewWithHeaderAndFooter(Context context) {
76 super(context);
77 initHeaderGridView();
78 }
79
80 public GridViewWithHeaderAndFooter(Context context, AttributeSet attrs) {
81 super(context, attrs);
82 initHeaderGridView();
83 }
84
85 public GridViewWithHeaderAndFooter(Context context, AttributeSet attrs, int defStyle) {
86 super(context, attrs, defStyle);
87 initHeaderGridView();
88 }
89
90 @Override
91 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
92 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
93 ListAdapter adapter = getAdapter();
94 if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
95 ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumnsCompatible());
96 ((HeaderViewGridAdapter) adapter).setRowHeight(getRowHeight());
97 }
98 }
99
100 @Override
101 public void setClipChildren(boolean clipChildren) {
102 // Ignore, since the header rows depend on not being clipped
103 }
104
105 /**
106 * Do not call this method unless you know how it works.
107 *
108 * @param clipChildren
109 */
110 public void setClipChildrenSupper(boolean clipChildren) {
111 super.setClipChildren(false);
112 }
113
114 /**
115 * Add a fixed view to appear at the top of the grid. If addHeaderView is
116 * called more than once, the views will appear in the order they were
117 * added. Views added using this call can take focus if they want.
118 * <p/>
119 * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
120 * the supplied cursor with one that will also account for header views.
121 *
122 * @param v The view to add.
123 */
124 public void addHeaderView(View v) {
125 addHeaderView(v, null, true);
126 }
127
128 /**
129 * Add a fixed view to appear at the top of the grid. If addHeaderView is
130 * called more than once, the views will appear in the order they were
131 * added. Views added using this call can take focus if they want.
132 * <p/>
133 * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
134 * the supplied cursor with one that will also account for header views.
135 *
136 * @param v The view to add.
137 * @param data Data to associate with this view
138 * @param isSelectable whether the item is selectable
139 */
140 public void addHeaderView(View v, Object data, boolean isSelectable) {
141 ListAdapter adapter = getAdapter();
142 if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) {
143 throw new IllegalStateException(
144 "Cannot add header view to grid -- setAdapter has already been called.");
145 }
146
147 ViewGroup.LayoutParams lyp = v.getLayoutParams();
148
149 FixedViewInfo info = new FixedViewInfo();
150 FrameLayout fl = new FullWidthFixedViewLayout(getContext());
151
152 if (lyp != null) {
153 v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
154 fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
155 }
156 fl.addView(v);
157 info.view = v;
158 info.viewContainer = fl;
159 info.data = data;
160 info.isSelectable = isSelectable;
161 mHeaderViewInfos.add(info);
162 // in the case of re-adding a header view, or adding one later on,
163 // we need to notify the observer
164 if (adapter != null) {
165 ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
166 }
167 }
168
169 public void addFooterView(View v) {
170 addFooterView(v, null, true);
171 }
172
173 public void addFooterView(View v, Object data, boolean isSelectable) {
174 ListAdapter mAdapter = getAdapter();
175 if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) {
176 throw new IllegalStateException(
177 "Cannot add header view to grid -- setAdapter has already been called.");
178 }
179
180 ViewGroup.LayoutParams lyp = v.getLayoutParams();
181
182 FixedViewInfo info = new FixedViewInfo();
183 FrameLayout fl = new FullWidthFixedViewLayout(getContext());
184
185 if (lyp != null) {
186 v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
187 fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
188 }
189 fl.addView(v);
190 info.view = v;
191 info.viewContainer = fl;
192 info.data = data;
193 info.isSelectable = isSelectable;
194 mFooterViewInfos.add(info);
195
196 if (mAdapter != null) {
197 ((HeaderViewGridAdapter) mAdapter).notifyDataSetChanged();
198 }
199 }
200
201 public int getHeaderViewCount() {
202 return mHeaderViewInfos.size();
203 }
204
205 public int getFooterViewCount() {
206 return mFooterViewInfos.size();
207 }
208
209 /**
210 * Removes a previously-added header view.
211 *
212 * @param v The view to remove
213 * @return true if the view was removed, false if the view was not a header
214 * view
215 */
216 public boolean removeHeaderView(View v) {
217 if (mHeaderViewInfos.size() > 0) {
218 boolean result = false;
219 ListAdapter adapter = getAdapter();
220 if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {
221 result = true;
222 }
223 removeFixedViewInfo(v, mHeaderViewInfos);
224 return result;
225 }
226 return false;
227 }
228
229 /**
230 * Removes a previously-added footer view.
231 *
232 * @param v The view to remove
233 * @return true if the view was removed, false if the view was not a header
234 * view
235 */
236 public boolean removeFooterView(View v) {
237 if (mFooterViewInfos.size() > 0) {
238 boolean result = false;
239 ListAdapter adapter = getAdapter();
240 if (adapter != null && ((HeaderViewGridAdapter) adapter).removeFooter(v)) {
241 result = true;
242 }
243 removeFixedViewInfo(v, mFooterViewInfos);
244 return result;
245 }
246 return false;
247 }
248
249 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
250 int len = where.size();
251 for (int i = 0; i < len; ++i) {
252 FixedViewInfo info = where.get(i);
253 if (info.view == v) {
254 where.remove(i);
255 break;
256 }
257 }
258 }
259
260 @TargetApi(11)
261 private int getNumColumnsCompatible() {
262 if (Build.VERSION.SDK_INT >= 11) {
263 return super.getNumColumns();
264 } else {
265 try {
266 Field numColumns = getClass().getSuperclass().getDeclaredField("mNumColumns");
267 numColumns.setAccessible(true);
268 return numColumns.getInt(this);
269 } catch (Exception e) {
270 if (mNumColumns != -1) {
271 return mNumColumns;
272 }
273 throw new RuntimeException("Can not determine the mNumColumns for this API platform, please call setNumColumns to set it.");
274 }
275 }
276 }
277
278 @TargetApi(16)
279 private int getColumnWidthCompatible() {
280 if (Build.VERSION.SDK_INT >= 16) {
281 return super.getColumnWidth();
282 } else {
283 try {
284 Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth");
285 numColumns.setAccessible(true);
286 return numColumns.getInt(this);
287 } catch (NoSuchFieldException e) {
288 throw new RuntimeException(e);
289 } catch (IllegalAccessException e) {
290 throw new RuntimeException(e);
291 }
292 }
293 }
294
295 @Override
296 protected void onDetachedFromWindow() {
297 super.onDetachedFromWindow();
298 mViewForMeasureRowHeight = null;
299 }
300
301 public void invalidateRowHeight() {
302 mRowHeight = -1;
303 }
304
305 public int getRowHeight() {
306 if (mRowHeight > 0) {
307 return mRowHeight;
308 }
309 ListAdapter adapter = getAdapter();
310 int numColumns = getNumColumnsCompatible();
311
312 // adapter has not been set or has no views in it;
313 if (adapter == null || adapter.getCount() <= numColumns * (mHeaderViewInfos.size() + mFooterViewInfos.size())) {
314 return -1;
315 }
316 int mColumnWidth = getColumnWidthCompatible();
317 View view = getAdapter().getView(numColumns * mHeaderViewInfos.size(), mViewForMeasureRowHeight, this);
318 AbsListView.LayoutParams p = (AbsListView.LayoutParams) view.getLayoutParams();
319 if (p == null) {
320 p = new AbsListView.LayoutParams(-1, -2, 0);
321 view.setLayoutParams(p);
322 }
323 int childHeightSpec = getChildMeasureSpec(
324 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
325 int childWidthSpec = getChildMeasureSpec(
326 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
327 view.measure(childWidthSpec, childHeightSpec);
328 mViewForMeasureRowHeight = view;
329 mRowHeight = view.getMeasuredHeight();
330 return mRowHeight;
331 }
332
333 @TargetApi(11)
334 public void tryToScrollToBottomSmoothly() {
335 int lastPos = getAdapter().getCount() - 1;
336 if (Build.VERSION.SDK_INT >= 11) {
337 smoothScrollToPositionFromTop(lastPos, 0);
338 } else {
339 setSelection(lastPos);
340 }
341 }
342
343 @TargetApi(11)
344 public void tryToScrollToBottomSmoothly(int duration) {
345 int lastPos = getAdapter().getCount() - 1;
346 if (Build.VERSION.SDK_INT >= 11) {
347 smoothScrollToPositionFromTop(lastPos, 0, duration);
348 } else {
349 setSelection(lastPos);
350 }
351 }
352
353 @Override
354 public void setAdapter(ListAdapter adapter) {
355 if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
356 HeaderViewGridAdapter headerViewGridAdapter = new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
357 int numColumns = getNumColumnsCompatible();
358 if (numColumns > 1) {
359 headerViewGridAdapter.setNumColumns(numColumns);
360 }
361 headerViewGridAdapter.setRowHeight(getRowHeight());
362 super.setAdapter(headerViewGridAdapter);
363 } else {
364 super.setAdapter(adapter);
365 }
366 }
367
368 /**
369 * full width
370 */
371 private class FullWidthFixedViewLayout extends FrameLayout {
372
373 public FullWidthFixedViewLayout(Context context) {
374 super(context);
375 }
376
377 @Override
378 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
379 int realLeft = GridViewWithHeaderAndFooter.this.getPaddingLeft() + getPaddingLeft();
380 // Try to make where it should be, from left, full width
381 if (realLeft != left) {
382 offsetLeftAndRight(realLeft - left);
383 }
384 super.onLayout(changed, left, top, right, bottom);
385 }
386
387 @Override
388 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
389 int targetWidth = GridViewWithHeaderAndFooter.this.getMeasuredWidth()
390 - GridViewWithHeaderAndFooter.this.getPaddingLeft()
391 - GridViewWithHeaderAndFooter.this.getPaddingRight();
392 widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
393 MeasureSpec.getMode(widthMeasureSpec));
394 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
395 }
396 }
397
398 @Override
399 public void setNumColumns(int numColumns) {
400 super.setNumColumns(numColumns);
401 mNumColumns = numColumns;
402 ListAdapter adapter = getAdapter();
403 if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
404 ((HeaderViewGridAdapter) adapter).setNumColumns(numColumns);
405 }
406 }
407
408 /**
409 * ListAdapter used when a HeaderGridView has header views. This ListAdapter
410 * wraps another one and also keeps track of the header views and their
411 * associated data objects.
412 * <p>This is intended as a base class; you will probably not need to
413 * use this class directly in your own code.
414 */
415 private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable {
416 // This is used to notify the container of updates relating to number of columns
417 // or headers changing, which changes the number of placeholders needed
418 private final DataSetObservable mDataSetObservable = new DataSetObservable();
419 private final ListAdapter mAdapter;
420 static final ArrayList<FixedViewInfo> EMPTY_INFO_LIST =
421 new ArrayList<FixedViewInfo>();
422
423 // This ArrayList is assumed to NOT be null.
424 ArrayList<FixedViewInfo> mHeaderViewInfos;
425 ArrayList<FixedViewInfo> mFooterViewInfos;
426 private int mNumColumns = 1;
427 private int mRowHeight = -1;
428 boolean mAreAllFixedViewsSelectable;
429 private final boolean mIsFilterable;
430 private boolean mCachePlaceHoldView = true;
431 // From Recycle Bin or calling getView, this a question...
432 private boolean mCacheFirstHeaderView = false;
433
434 public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ArrayList<FixedViewInfo> footViewInfos, ListAdapter adapter) {
435 mAdapter = adapter;
436 mIsFilterable = adapter instanceof Filterable;
437 if (headerViewInfos == null) {
438 mHeaderViewInfos = EMPTY_INFO_LIST;
439 } else {
440 mHeaderViewInfos = headerViewInfos;
441 }
442
443 if (footViewInfos == null) {
444 mFooterViewInfos = EMPTY_INFO_LIST;
445 } else {
446 mFooterViewInfos = footViewInfos;
447 }
448 mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos)
449 && areAllListInfosSelectable(mFooterViewInfos);
450 }
451
452 public void setNumColumns(int numColumns) {
453 if (numColumns < 1) {
454 return;
455 }
456 if (mNumColumns != numColumns) {
457 mNumColumns = numColumns;
458 notifyDataSetChanged();
459 }
460 }
461
462 public void setRowHeight(int height) {
463 mRowHeight = height;
464 }
465
466 public int getHeadersCount() {
467 return mHeaderViewInfos.size();
468 }
469
470 public int getFootersCount() {
471 return mFooterViewInfos.size();
472 }
473
474 @Override
475 public boolean isEmpty() {
476 return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0 && getFootersCount() == 0;
477 }
478
479 private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
480 if (infos != null) {
481 for (FixedViewInfo info : infos) {
482 if (!info.isSelectable) {
483 return false;
484 }
485 }
486 }
487 return true;
488 }
489
490 public boolean removeHeader(View v) {
491 for (int i = 0; i < mHeaderViewInfos.size(); i++) {
492 FixedViewInfo info = mHeaderViewInfos.get(i);
493 if (info.view == v) {
494 mHeaderViewInfos.remove(i);
495 mAreAllFixedViewsSelectable =
496 areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos);
497 mDataSetObservable.notifyChanged();
498 return true;
499 }
500 }
501 return false;
502 }
503
504 public boolean removeFooter(View v) {
505 for (int i = 0; i < mFooterViewInfos.size(); i++) {
506 FixedViewInfo info = mFooterViewInfos.get(i);
507 if (info.view == v) {
508 mFooterViewInfos.remove(i);
509 mAreAllFixedViewsSelectable =
510 areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos);
511 mDataSetObservable.notifyChanged();
512 return true;
513 }
514 }
515 return false;
516 }
517
518 @Override
519 public int getCount() {
520 if (mAdapter != null) {
521 return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount();
522 } else {
523 return (getFootersCount() + getHeadersCount()) * mNumColumns;
524 }
525 }
526
527 @Override
528 public boolean areAllItemsEnabled() {
529 if (mAdapter != null) {
530 return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
531 } else {
532 return true;
533 }
534 }
535
536 private int getAdapterAndPlaceHolderCount() {
537 final int adapterCount = (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns);
538 return adapterCount;
539 }
540
541 @Override
542 public boolean isEnabled(int position) {
543 // Header (negative positions will throw an IndexOutOfBoundsException)
544 int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
545 if (position < numHeadersAndPlaceholders) {
546 return position % mNumColumns == 0
547 && mHeaderViewInfos.get(position / mNumColumns).isSelectable;
548 }
549
550 // Adapter
551 final int adjPosition = position - numHeadersAndPlaceholders;
552 int adapterCount = 0;
553 if (mAdapter != null) {
554 adapterCount = getAdapterAndPlaceHolderCount();
555 if (adjPosition < adapterCount) {
556 return adjPosition < mAdapter.getCount() && mAdapter.isEnabled(adjPosition);
557 }
558 }
559
560 // Footer (off-limits positions will throw an IndexOutOfBoundsException)
561 final int footerPosition = adjPosition - adapterCount;
562 return footerPosition % mNumColumns == 0
563 && mFooterViewInfos.get(footerPosition / mNumColumns).isSelectable;
564 }
565
566 @Override
567 public Object getItem(int position) {
568 // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
569 int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
570 if (position < numHeadersAndPlaceholders) {
571 if (position % mNumColumns == 0) {
572 return mHeaderViewInfos.get(position / mNumColumns).data;
573 }
574 return null;
575 }
576
577 // Adapter
578 final int adjPosition = position - numHeadersAndPlaceholders;
579 int adapterCount = 0;
580 if (mAdapter != null) {
581 adapterCount = getAdapterAndPlaceHolderCount();
582 if (adjPosition < adapterCount) {
583 if (adjPosition < mAdapter.getCount()) {
584 return mAdapter.getItem(adjPosition);
585 } else {
586 return null;
587 }
588 }
589 }
590
591 // Footer (off-limits positions will throw an IndexOutOfBoundsException)
592 final int footerPosition = adjPosition - adapterCount;
593 if (footerPosition % mNumColumns == 0) {
594 return mFooterViewInfos.get(footerPosition).data;
595 } else {
596 return null;
597 }
598 }
599
600 @Override
601 public long getItemId(int position) {
602 int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
603 if (mAdapter != null && position >= numHeadersAndPlaceholders) {
604 int adjPosition = position - numHeadersAndPlaceholders;
605 int adapterCount = mAdapter.getCount();
606 if (adjPosition < adapterCount) {
607 return mAdapter.getItemId(adjPosition);
608 }
609 }
610 return -1;
611 }
612
613 @Override
614 public boolean hasStableIds() {
615 if (mAdapter != null) {
616 return mAdapter.hasStableIds();
617 }
618 return false;
619 }
620
621 @Override
622 public View getView(int position, View convertView, ViewGroup parent) {
623 if (DEBUG) {
624 Log.d(LOG_TAG, String.format("getView: %s, reused: %s", position, convertView == null));
625 }
626 // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
627 int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
628 if (position < numHeadersAndPlaceholders) {
629 View headerViewContainer = mHeaderViewInfos
630 .get(position / mNumColumns).viewContainer;
631 if (position % mNumColumns == 0) {
632 return headerViewContainer;
633 } else {
634 if (convertView == null) {
635 convertView = new View(parent.getContext());
636 }
637 // We need to do this because GridView uses the height of the last item
638 // in a row to determine the height for the entire row.
639 convertView.setVisibility(View.INVISIBLE);
640 convertView.setMinimumHeight(headerViewContainer.getHeight());
641 return convertView;
642 }
643 }
644 // Adapter
645 final int adjPosition = position - numHeadersAndPlaceholders;
646 int adapterCount = 0;
647 if (mAdapter != null) {
648 adapterCount = getAdapterAndPlaceHolderCount();
649 if (adjPosition < adapterCount) {
650 if (adjPosition < mAdapter.getCount()) {
651 View view = mAdapter.getView(adjPosition, convertView, parent);
652 return view;
653 } else {
654 if (convertView == null) {
655 convertView = new View(parent.getContext());
656 }
657 convertView.setVisibility(View.INVISIBLE);
658 convertView.setMinimumHeight(mRowHeight);
659 return convertView;
660 }
661 }
662 }
663 // Footer
664 final int footerPosition = adjPosition - adapterCount;
665 if (footerPosition < getCount()) {
666 View footViewContainer = mFooterViewInfos
667 .get(footerPosition / mNumColumns).viewContainer;
668 if (position % mNumColumns == 0) {
669 return footViewContainer;
670 } else {
671 if (convertView == null) {
672 convertView = new View(parent.getContext());
673 }
674 // We need to do this because GridView uses the height of the last item
675 // in a row to determine the height for the entire row.
676 convertView.setVisibility(View.INVISIBLE);
677 convertView.setMinimumHeight(footViewContainer.getHeight());
678 return convertView;
679 }
680 }
681 throw new ArrayIndexOutOfBoundsException(position);
682 }
683
684 @Override
685 public int getItemViewType(int position) {
686
687 final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
688 final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getViewTypeCount() - 1;
689 int type = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
690 if (mCachePlaceHoldView) {
691 // Header
692 if (position < numHeadersAndPlaceholders) {
693 if (position == 0) {
694 if (mCacheFirstHeaderView) {
695 type = adapterViewTypeStart + mHeaderViewInfos.size() + mFooterViewInfos.size() + 1 + 1;
696 }
697 }
698 if (position % mNumColumns != 0) {
699 type = adapterViewTypeStart + (position / mNumColumns + 1);
700 }
701 }
702 }
703
704 // Adapter
705 final int adjPosition = position - numHeadersAndPlaceholders;
706 int adapterCount = 0;
707 if (mAdapter != null) {
708 adapterCount = getAdapterAndPlaceHolderCount();
709 if (adjPosition >= 0 && adjPosition < adapterCount) {
710 if (adjPosition < mAdapter.getCount()) {
711 type = mAdapter.getItemViewType(adjPosition);
712 } else {
713 if (mCachePlaceHoldView) {
714 type = adapterViewTypeStart + mHeaderViewInfos.size() + 1;
715 }
716 }
717 }
718 }
719
720 if (mCachePlaceHoldView) {
721 // Footer
722 final int footerPosition = adjPosition - adapterCount;
723 if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) {
724 type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1);
725 }
726 }
727 if (DEBUG) {
728 Log.d(LOG_TAG, String.format("getItemViewType: pos: %s, result: %s", position, type, mCachePlaceHoldView, mCacheFirstHeaderView));
729 }
730 return type;
731 }
732
733 /**
734 * content view, content view holder, header[0], header and footer placeholder(s)
735 *
736 * @return
737 */
738 @Override
739 public int getViewTypeCount() {
740 int count = mAdapter == null ? 1 : mAdapter.getViewTypeCount();
741 if (mCachePlaceHoldView) {
742 int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size();
743 if (mCacheFirstHeaderView) {
744 offset += 1;
745 }
746 count += offset;
747 }
748 if (DEBUG) {
749 Log.d(LOG_TAG, String.format("getViewTypeCount: %s", count));
750 }
751 return count;
752 }
753
754 @Override
755 public void registerDataSetObserver(DataSetObserver observer) {
756 mDataSetObservable.registerObserver(observer);
757 if (mAdapter != null) {
758 mAdapter.registerDataSetObserver(observer);
759 }
760 }
761
762 @Override
763 public void unregisterDataSetObserver(DataSetObserver observer) {
764 mDataSetObservable.unregisterObserver(observer);
765 if (mAdapter != null) {
766 mAdapter.unregisterDataSetObserver(observer);
767 }
768 }
769
770 @Override
771 public Filter getFilter() {
772 if (mIsFilterable) {
773 return ((Filterable) mAdapter).getFilter();
774 }
775 return null;
776 }
777
778 @Override
779 public ListAdapter getWrappedAdapter() {
780 return mAdapter;
781 }
782
783 public void notifyDataSetChanged() {
784 mDataSetObservable.notifyChanged();
785 }
786 }
787 }