GridViewWithHeaderAndFooter.java 31 KB

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