/*
* Copyright 2013 Hari Krishna Dulipudi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.dworks.libs.astickyheader;
import java.util.Arrays;
import java.util.Comparator;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.ListView;
import dev.dworks.libs.actionbarplus.widget.PinnedSectionGridView;
import dev.dworks.libs.actionbarplus.widget.PinnedSectionGridView.PinnedSectionGridAdapter;
public abstract class SectionedGridAdapter extends BaseAdapter implements PinnedSectionGridAdapter {
protected static final int TYPE_NORMAL_CELL = 0;
protected static final int TYPE_FILLER = -1;
protected static final int TYPE_HEADER = -2;
protected static final int TYPE_HEADER_FILLER = -3;
private boolean mValid = true;
private ListAdapter mBaseAdapter;
private SparseArray<Section> mSections = new SparseArray<Section>();
private Context mContext;
private int mNumColumns;
private int mWidth;
private int mColumnWidth;
private int mHorizontalSpacing;
private int mStrechMode;
private int requestedColumnWidth;
private int requestedHorizontalSpacing;
private GridView mGridView;
private int mHeaderHeight;
private int mNormalCellHeight;
public SectionedGridAdapter(final Context context, final BaseAdapter baseAdapter) {
mHeaderHeight = getHeaderHeight();
mNormalCellHeight = getNormalCellHeight();
// mNormalCellWidth = getNormalCellWidth();
mBaseAdapter = baseAdapter;
mContext = context;
mBaseAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
mValid = !mBaseAdapter.isEmpty();
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
mValid = false;
notifyDataSetInvalidated();
}
});
}
public void setGridView(GridView gridView) {
if (!(gridView instanceof PinnedSectionGridView)) {
throw new IllegalArgumentException("Does your grid view extends PinnedSectionGridView?");
}
mGridView = gridView;
mStrechMode = gridView.getStretchMode();
mWidth = gridView.getWidth() - (mGridView.getPaddingLeft() + mGridView.getPaddingRight());
mNumColumns = ((PinnedSectionGridView) gridView).getNumColumns();
requestedColumnWidth = ((PinnedSectionGridView) gridView).getColumnWidth();
requestedHorizontalSpacing = ((PinnedSectionGridView) gridView).getHorizontalSpacing();
}
private int getHeaderSize() {
if (mWidth != mGridView.getWidth()) {
mStrechMode = mGridView.getStretchMode();
mWidth = mGridView.getWidth() - (mGridView.getPaddingLeft() + mGridView.getPaddingRight());
mNumColumns = ((PinnedSectionGridView) mGridView).getNumColumns();
requestedColumnWidth = ((PinnedSectionGridView) mGridView).getColumnWidth();
requestedHorizontalSpacing = ((PinnedSectionGridView) mGridView).getHorizontalSpacing();
}
final int spaceLeftOver = mWidth - mNumColumns * requestedColumnWidth - (mNumColumns - 1)
* requestedHorizontalSpacing;
switch (mStrechMode) {
case GridView.NO_STRETCH: // Nobody stretches
mWidth -= spaceLeftOver;
mColumnWidth = requestedColumnWidth;
mHorizontalSpacing = requestedHorizontalSpacing;
break;
case GridView.STRETCH_COLUMN_WIDTH:
mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
mHorizontalSpacing = requestedHorizontalSpacing;
break;
case GridView.STRETCH_SPACING:
mColumnWidth = requestedColumnWidth;
if (mNumColumns > 1) {
mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver / (mNumColumns - 1);
} else {
mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
}
break;
case GridView.STRETCH_SPACING_UNIFORM:
mColumnWidth = requestedColumnWidth;
mHorizontalSpacing = requestedHorizontalSpacing;
mWidth = mWidth - spaceLeftOver + 2 * mHorizontalSpacing;
break;
}
return mWidth + (mNumColumns - 1) * (mColumnWidth + mHorizontalSpacing);
}
public void setSections(final Section[] sections) {
mSections.clear();
Arrays.sort(sections, new Comparator<Section>() {
@Override
public int compare(final Section o, final Section o1) {
return o.firstPosition == o1.firstPosition ? 0 : o.firstPosition < o1.firstPosition ? -1 : 1;
}
});
int offset = 0; // offset positions for the headers we're adding
for (int i = 0; i < sections.length; i++) {
final Section section = sections[i];
Section sectionAdd;
for (int j = 0; j < mNumColumns - 1; j++) {
sectionAdd = new Section(section.firstPosition, section.title);
sectionAdd.type = TYPE_HEADER_FILLER;
sectionAdd.sectionedPosition = sectionAdd.firstPosition + offset;
mSections.append(sectionAdd.sectionedPosition, sectionAdd);
++offset;
}
sectionAdd = new Section(section.firstPosition, section.title);
sectionAdd.type = TYPE_HEADER;
sectionAdd.sectionedPosition = sectionAdd.firstPosition + offset;
mSections.append(sectionAdd.sectionedPosition, sectionAdd);
++offset;
if (i + 1 < sections.length) {
final int nextPos = sections[i + 1].firstPosition;
final int itemsCount = nextPos - section.firstPosition;
final int dummyCount = mNumColumns - itemsCount % mNumColumns;
if (mNumColumns != dummyCount) {
for (int j = 0; j < dummyCount; j++) {
sectionAdd = new Section(section.firstPosition, section.title);
sectionAdd.type = TYPE_FILLER;
sectionAdd.sectionedPosition = nextPos + offset;
mSections.append(sectionAdd.sectionedPosition, sectionAdd);
++offset;
}
}
}
}
notifyDataSetChanged();
}
public int positionToSectionedPosition(final int position) {
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).firstPosition > position) {
break;
}
++offset;
}
return position + offset;
}
public int sectionedPositionToPosition(final int sectionedPosition) {
if (isSectionHeaderPosition(sectionedPosition)) {
return ListView.INVALID_POSITION;
}
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
break;
}
--offset;
}
return sectionedPosition + offset;
}
public boolean isSectionHeaderPosition(final int position) {
return mSections.get(position) != null;
}
@Override
public int getCount() {
return mValid ? mBaseAdapter.getCount() + mSections.size() : 0;
}
@Override
public Object getItem(final int position) {
return isSectionHeaderPosition(position) ? mSections.get(position) : mBaseAdapter
.getItem(sectionedPositionToPosition(position));
}
@Override
public long getItemId(final int position) {
return isSectionHeaderPosition(position) ? Integer.MAX_VALUE - mSections.indexOfKey(position) : mBaseAdapter
.getItemId(sectionedPositionToPosition(position));
}
@Override
public final int getItemViewType(final int position) {
if (!isSectionHeaderPosition(position))
return getItemViewTypeExtra(position);
final int type = mSections.get(position).type;
return type;
}
/**
* this allows whoever uses this adapter to customize additional cells types
*
* @param position
*/
protected int getItemViewTypeExtra(final int position) {
return TYPE_NORMAL_CELL;
}
@Override
public boolean isEnabled(final int position) {
return isSectionHeaderPosition(position) ? false : mBaseAdapter
.isEnabled(sectionedPositionToPosition(position));
}
@Override
public final int getViewTypeCount() {
// this includes: empty cells, header, header-filler, and extras (which includes at least the normal cells
return 3 + getViewTypeCountExtra();
}
public int getViewTypeCountExtra() {
return 1;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean hasStableIds() {
return mBaseAdapter.hasStableIds();
}
@Override
public boolean isEmpty() {
return mBaseAdapter.isEmpty();
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
View view = null;
if (isSectionHeaderPosition(position)) {
final Section section = mSections.get(position);
LayoutParams layoutParams;
switch (section.type) {
case TYPE_HEADER:
view = handleSectionHeaderView(convertView, section, parent);
layoutParams = view.getLayoutParams();
layoutParams.width = getHeaderSize();
view.setLayoutParams(layoutParams);
view.setVisibility(View.VISIBLE);
break;
case TYPE_HEADER_FILLER:
view = convertView;
if (view == null) {
view = new View(mContext);
view.setLayoutParams(new AbsListView.LayoutParams(0, mHeaderHeight));
}
break;
case TYPE_FILLER:
view = convertView;
if (view == null) {
view = new View(mContext);
view.setLayoutParams(new AbsListView.LayoutParams(0, mNormalCellHeight));
}
final boolean lastInRow = position % mNumColumns == mNumColumns - 1;
layoutParams = view.getLayoutParams();
layoutParams.width = lastInRow ? LayoutParams.MATCH_PARENT : 0;
view.setLayoutParams(layoutParams);
break;
}
} else {
view = mBaseAdapter.getView(sectionedPositionToPosition(position), convertView, parent);
final LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = mNormalCellHeight;
layoutParams.width = mColumnWidth;
view.setLayoutParams(layoutParams);
}
return view;
}
protected abstract View handleSectionHeaderView(final View convertView, final Section section, ViewGroup parent);
protected abstract int getHeaderHeight();
protected abstract int getNormalCellHeight();
@Override
public boolean isItemViewTypePinned(final int position) {
final Section section = mSections.get(position);
return isSectionHeaderPosition(position) && section.type == TYPE_HEADER;
}
// ///////////////////////////////////////////////////
// Section //
// //////////
public static class Section {
int firstPosition;
int sectionedPosition;
CharSequence title;
int type = 0;
public Section(final int firstPosition, final CharSequence title) {
this.firstPosition = firstPosition;
this.title = title;
}
public CharSequence getTitle() {
return title;
}
}
// ///////////////////////////////////////////////////
// ViewHolder //
// /////////////
public static class ViewHolder {
@SuppressWarnings("unchecked")
public static <T extends View> T get(final View view, final int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
}
}