GridViewWithHeaderAndFooter.java 31 KB

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