();
58 |
59 | private void initHeaderGridView() {
60 | super.setClipChildren(false);
61 | }
62 |
63 | public HeaderGridView(Context context) {
64 | super(context);
65 | initHeaderGridView();
66 | }
67 |
68 | public HeaderGridView(Context context, AttributeSet attrs) {
69 | super(context, attrs);
70 | initHeaderGridView();
71 | }
72 |
73 | public HeaderGridView(Context context, AttributeSet attrs, int defStyle) {
74 | super(context, attrs, defStyle);
75 | initHeaderGridView();
76 | }
77 |
78 | @Override
79 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
80 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
81 | ListAdapter adapter = getAdapter();
82 | if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
83 | ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumns());
84 | }
85 | }
86 |
87 | @Override
88 | public void setClipChildren(boolean clipChildren) {
89 | // Ignore, since the header rows depend on not being clipped
90 | }
91 |
92 | /**
93 | * Add a fixed view to appear at the top of the grid. If addHeaderView is
94 | * called more than once, the views will appear in the order they were
95 | * added. Views added using this call can take focus if they want.
96 | *
97 | * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
98 | * the supplied cursor with one that will also account for header views.
99 | *
100 | * @param v The view to add.
101 | * @param data Data to associate with this view
102 | * @param isSelectable whether the item is selectable
103 | */
104 | public void addHeaderView(View v, Object data, boolean isSelectable) {
105 | ListAdapter adapter = getAdapter();
106 |
107 | if (adapter != null && ! (adapter instanceof HeaderViewGridAdapter)) {
108 | throw new IllegalStateException(
109 | "Cannot add header view to grid -- setAdapter has already been called.");
110 | }
111 |
112 | FixedViewInfo info = new FixedViewInfo();
113 | FrameLayout fl = new FullWidthFixedViewLayout(getContext());
114 | fl.addView(v);
115 | info.view = v;
116 | info.viewContainer = fl;
117 | info.data = data;
118 | info.isSelectable = isSelectable;
119 | mHeaderViewInfos.add(info);
120 |
121 | // in the case of re-adding a header view, or adding one later on,
122 | // we need to notify the observer
123 | if (adapter != null) {
124 | ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
125 | }
126 | }
127 |
128 | /**
129 | * Add a fixed view to appear at the top of the grid. If addHeaderView is
130 | * called more than once, the views will appear in the order they were
131 | * added. Views added using this call can take focus if they want.
132 | *
133 | * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
134 | * the supplied cursor with one that will also account for header views.
135 | *
136 | * @param v The view to add.
137 | */
138 | public void addHeaderView(View v) {
139 | addHeaderView(v, null, true);
140 | }
141 |
142 | public int getHeaderViewCount() {
143 | return mHeaderViewInfos.size();
144 | }
145 |
146 | /**
147 | * Removes a previously-added header view.
148 | *
149 | * @param v The view to remove
150 | * @return true if the view was removed, false if the view was not a header
151 | * view
152 | */
153 | public boolean removeHeaderView(View v) {
154 | if (mHeaderViewInfos.size() > 0) {
155 | boolean result = false;
156 | ListAdapter adapter = getAdapter();
157 | if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {
158 | result = true;
159 | }
160 | removeFixedViewInfo(v, mHeaderViewInfos);
161 | return result;
162 | }
163 | return false;
164 | }
165 |
166 | private void removeFixedViewInfo(View v, ArrayList where) {
167 | int len = where.size();
168 | for (int i = 0; i < len; ++i) {
169 | FixedViewInfo info = where.get(i);
170 | if (info.view == v) {
171 | where.remove(i);
172 | break;
173 | }
174 | }
175 | }
176 |
177 | @Override
178 | public void setAdapter(ListAdapter adapter) {
179 | if (mHeaderViewInfos.size() > 0) {
180 | HeaderViewGridAdapter hadapter = new HeaderViewGridAdapter(mHeaderViewInfos, adapter);
181 | int numColumns = getNumColumns();
182 | if (numColumns > 1) {
183 | hadapter.setNumColumns(numColumns);
184 | }
185 | super.setAdapter(hadapter);
186 | } else {
187 | super.setAdapter(adapter);
188 | }
189 | }
190 |
191 | private class FullWidthFixedViewLayout extends FrameLayout {
192 | public FullWidthFixedViewLayout(Context context) {
193 | super(context);
194 | }
195 |
196 | @Override
197 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
198 | int targetWidth = HeaderGridView.this.getMeasuredWidth()
199 | - HeaderGridView.this.getPaddingLeft()
200 | - HeaderGridView.this.getPaddingRight();
201 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
202 | MeasureSpec.getMode(widthMeasureSpec));
203 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
204 | }
205 | }
206 |
207 | /**
208 | * ListAdapter used when a HeaderGridView has header views. This ListAdapter
209 | * wraps another one and also keeps track of the header views and their
210 | * associated data objects.
211 | *This is intended as a base class; you will probably not need to
212 | * use this class directly in your own code.
213 | */
214 | public static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable {
215 |
216 | // This is used to notify the container of updates relating to number of columns
217 | // or headers changing, which changes the number of placeholders needed
218 | private final DataSetObservable mDataSetObservable = new DataSetObservable();
219 |
220 | private final ListAdapter mAdapter;
221 | private int mNumColumns = 1;
222 |
223 | // This ArrayList is assumed to NOT be null.
224 | ArrayList mHeaderViewInfos;
225 |
226 | boolean mAreAllFixedViewsSelectable;
227 |
228 | private final boolean mIsFilterable;
229 |
230 | public HeaderViewGridAdapter(ArrayList headerViewInfos, ListAdapter adapter) {
231 | mAdapter = adapter;
232 | mIsFilterable = adapter instanceof Filterable;
233 |
234 | if (headerViewInfos == null) {
235 | throw new IllegalArgumentException("headerViewInfos cannot be null");
236 | }
237 | mHeaderViewInfos = headerViewInfos;
238 |
239 | mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);
240 | }
241 |
242 | public int getHeadersCount() {
243 | return mHeaderViewInfos.size();
244 | }
245 |
246 | @Override
247 | public boolean isEmpty() {
248 | return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0;
249 | }
250 |
251 | public void setNumColumns(int numColumns) {
252 | if (numColumns < 1) {
253 | throw new IllegalArgumentException("Number of columns must be 1 or more");
254 | }
255 | if (mNumColumns != numColumns) {
256 | mNumColumns = numColumns;
257 | notifyDataSetChanged();
258 | }
259 | }
260 |
261 | private boolean areAllListInfosSelectable(ArrayList infos) {
262 | if (infos != null) {
263 | for (FixedViewInfo info : infos) {
264 | if (!info.isSelectable) {
265 | return false;
266 | }
267 | }
268 | }
269 | return true;
270 | }
271 |
272 | public boolean removeHeader(View v) {
273 | for (int i = 0; i < mHeaderViewInfos.size(); i++) {
274 | FixedViewInfo info = mHeaderViewInfos.get(i);
275 | if (info.view == v) {
276 | mHeaderViewInfos.remove(i);
277 |
278 | mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);
279 |
280 | mDataSetObservable.notifyChanged();
281 | return true;
282 | }
283 | }
284 |
285 | return false;
286 | }
287 |
288 | @Override
289 | public int getCount() {
290 | if (mAdapter != null) {
291 | return getHeadersCount() * mNumColumns + mAdapter.getCount();
292 | } else {
293 | return getHeadersCount() * mNumColumns;
294 | }
295 | }
296 |
297 | @Override
298 | public boolean areAllItemsEnabled() {
299 | if (mAdapter != null) {
300 | return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
301 | } else {
302 | return true;
303 | }
304 | }
305 |
306 | @Override
307 | public boolean isEnabled(int position) {
308 | // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
309 | int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
310 | if (position < numHeadersAndPlaceholders) {
311 | return (position % mNumColumns == 0)
312 | && mHeaderViewInfos.get(position / mNumColumns).isSelectable;
313 | }
314 |
315 | // Adapter
316 | final int adjPosition = position - numHeadersAndPlaceholders;
317 | int adapterCount = 0;
318 | if (mAdapter != null) {
319 | adapterCount = mAdapter.getCount();
320 | if (adjPosition < adapterCount) {
321 | return mAdapter.isEnabled(adjPosition);
322 | }
323 | }
324 |
325 | throw new ArrayIndexOutOfBoundsException(position);
326 | }
327 |
328 | @Override
329 | public Object getItem(int position) {
330 | // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
331 | int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
332 | if (position < numHeadersAndPlaceholders) {
333 | if (position % mNumColumns == 0) {
334 | return mHeaderViewInfos.get(position / mNumColumns).data;
335 | }
336 | return null;
337 | }
338 |
339 | // Adapter
340 | final int adjPosition = position - numHeadersAndPlaceholders;
341 | int adapterCount = 0;
342 | if (mAdapter != null) {
343 | adapterCount = mAdapter.getCount();
344 | if (adjPosition < adapterCount) {
345 | return mAdapter.getItem(adjPosition);
346 | }
347 | }
348 |
349 | throw new ArrayIndexOutOfBoundsException(position);
350 | }
351 |
352 | @Override
353 | public long getItemId(int position) {
354 | int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
355 | if (mAdapter != null && position >= numHeadersAndPlaceholders) {
356 | int adjPosition = position - numHeadersAndPlaceholders;
357 | int adapterCount = mAdapter.getCount();
358 | if (adjPosition < adapterCount) {
359 | return mAdapter.getItemId(adjPosition);
360 | }
361 | }
362 | return -1;
363 | }
364 |
365 | @Override
366 | public boolean hasStableIds() {
367 | if (mAdapter != null) {
368 | return mAdapter.hasStableIds();
369 | }
370 | return false;
371 | }
372 |
373 | @Override
374 | public View getView(int position, View convertView, ViewGroup parent) {
375 | // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
376 | int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns ;
377 | if (position < numHeadersAndPlaceholders) {
378 | View headerViewContainer = mHeaderViewInfos
379 | .get(position / mNumColumns).viewContainer;
380 | if (position % mNumColumns == 0) {
381 | return headerViewContainer;
382 | } else {
383 | if (convertView == null) {
384 | convertView = new View(parent.getContext());
385 | }
386 | // We need to do this because GridView uses the height of the last item
387 | // in a row to determine the height for the entire row.
388 | convertView.setVisibility(View.INVISIBLE);
389 | convertView.setMinimumHeight(headerViewContainer.getHeight());
390 | return convertView;
391 | }
392 | }
393 |
394 | // Adapter
395 | final int adjPosition = position - numHeadersAndPlaceholders;
396 | int adapterCount = 0;
397 | if (mAdapter != null) {
398 | adapterCount = mAdapter.getCount();
399 | if (adjPosition < adapterCount) {
400 | return mAdapter.getView(adjPosition, convertView, parent);
401 | }
402 | }
403 |
404 | throw new ArrayIndexOutOfBoundsException(position);
405 | }
406 |
407 | @Override
408 | public int getItemViewType(int position) {
409 | int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
410 | if (position < numHeadersAndPlaceholders && (position % mNumColumns != 0)) {
411 | // Placeholders get the last view type number
412 | return mAdapter != null ? mAdapter.getViewTypeCount() : 1;
413 | }
414 | if (mAdapter != null && position >= numHeadersAndPlaceholders) {
415 | int adjPosition = position - numHeadersAndPlaceholders;
416 | int adapterCount = mAdapter.getCount();
417 | if (adjPosition < adapterCount) {
418 | return mAdapter.getItemViewType(adjPosition);
419 | }
420 | }
421 |
422 | return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
423 | }
424 |
425 | @Override
426 | public int getViewTypeCount() {
427 | if (mAdapter != null) {
428 | return mAdapter.getViewTypeCount() + 1;
429 | }
430 | return 2;
431 | }
432 |
433 | @Override
434 | public void registerDataSetObserver(DataSetObserver observer) {
435 | mDataSetObservable.registerObserver(observer);
436 | if (mAdapter != null) {
437 | mAdapter.registerDataSetObserver(observer);
438 | }
439 | }
440 |
441 | @Override
442 | public void unregisterDataSetObserver(DataSetObserver observer) {
443 | mDataSetObservable.unregisterObserver(observer);
444 | if (mAdapter != null) {
445 | mAdapter.unregisterDataSetObserver(observer);
446 | }
447 | }
448 |
449 | @Override
450 | public Filter getFilter() {
451 | if (mIsFilterable) {
452 | return ((Filterable) mAdapter).getFilter();
453 | }
454 | return null;
455 | }
456 |
457 | @Override
458 | public ListAdapter getWrappedAdapter() {
459 | return mAdapter;
460 | }
461 |
462 | public void notifyDataSetChanged() {
463 | mDataSetObservable.notifyChanged();
464 | }
465 | }
466 | }
467 |
--------------------------------------------------------------------------------