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