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