/* * Copyright (C) 2015 AlexMofer * * 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 am.widget.headerfootergridview; import android.database.DataSetObservable; import android.database.DataSetObserver; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Filter; import android.widget.Filterable; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.WrapperListAdapter; import java.util.ArrayList; /** * ListAdapter used when a ListView has header views. This ListAdapter wraps * another one and also keeps track of the header views and their associated * data objects. * This is intended as a base class; you will probably not need to use this * class directly in your own code. */ @SuppressWarnings("WeakerAccess") public class HeaderFooterViewListAdapter implements WrapperListAdapter, Filterable { private static final ArrayList<HeaderFooterGridView.FixedViewInfo> EMPTY_INFO_LIST = new ArrayList<>(); private static final ArrayList<View> EMPTY_VIEW_LIST = new ArrayList<>(); private final ArrayList<HeaderFooterGridView.FixedViewInfo> mHeaderItemInfo; private final ArrayList<HeaderFooterGridView.FixedViewInfo> mFooterItemInfo; private final DataSetObservable mDataSetObservable = new DataSetObservable(); private final boolean mIsFilterable; private final ArrayList<View> mHeaderViews; private final ArrayList<View> mFooterViews; private boolean mAreAllFixedViewsSelectable; private ListAdapter mAdapter; private int mNumColumns; private int unusedPositionCount = 0; public HeaderFooterViewListAdapter( ArrayList<HeaderFooterGridView.FixedViewInfo> headerItemInfo, ArrayList<HeaderFooterGridView.FixedViewInfo> footerItemInfo, ListAdapter adapter, ArrayList<View> headerViews, ArrayList<View> footerViews, int numColumns) { mAdapter = adapter; if (headerViews == null) { mHeaderViews = EMPTY_VIEW_LIST; } else { mHeaderViews = headerViews; } if (footerViews == null) { mFooterViews = EMPTY_VIEW_LIST; } else { mFooterViews = footerViews; } mIsFilterable = adapter instanceof Filterable; if (headerItemInfo == null) { mHeaderItemInfo = EMPTY_INFO_LIST; } else { mHeaderItemInfo = headerItemInfo; } if (footerItemInfo == null) { mFooterItemInfo = EMPTY_INFO_LIST; } else { mFooterItemInfo = footerItemInfo; } mAreAllFixedViewsSelectable = areAllListInfoSelectable(mHeaderItemInfo) && areAllListInfoSelectable(mFooterItemInfo); if (numColumns > 0) mNumColumns = numColumns; else mNumColumns = 1; } public int getHeaderItemsCount() { return mHeaderItemInfo.size(); } public int getFooterItemsCount() { return mFooterItemInfo.size(); } @Override public boolean isEmpty() { return mAdapter == null || mAdapter.isEmpty(); } private boolean areAllListInfoSelectable(ArrayList<HeaderFooterGridView.FixedViewInfo> info) { if (mHeaderViews != null && mHeaderViews.size() > 0) return false; if (mFooterViews != null && mFooterViews.size() > 0) return false; if (info != null) { for (HeaderFooterGridView.FixedViewInfo i : info) { if (!i.isSelectable) { return false; } } } return true; } public boolean removeHeaderItem(View v) { for (int i = 0; i < mHeaderItemInfo.size(); i++) { HeaderFooterGridView.FixedViewInfo info = mHeaderItemInfo.get(i); if (info.view == v) { mHeaderItemInfo.remove(i); mAreAllFixedViewsSelectable = areAllListInfoSelectable(mHeaderItemInfo) && areAllListInfoSelectable(mFooterItemInfo); notifyDataSetChanged(); return true; } } return false; } public boolean removeFooterItem(View v) { for (int i = 0; i < mFooterItemInfo.size(); i++) { HeaderFooterGridView.FixedViewInfo info = mFooterItemInfo.get(i); if (info.view == v) { mFooterItemInfo.remove(i); mAreAllFixedViewsSelectable = areAllListInfoSelectable(mHeaderItemInfo) && areAllListInfoSelectable(mFooterItemInfo); notifyDataSetChanged(); return true; } } return false; } @Override public int getCount() { return getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount() + getFooterViewsCount(); } @Override public boolean areAllItemsEnabled() { return mAdapter == null || mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled(); } @Override public boolean isEnabled(int position) { if (position < getHeaderViewsCount()) { // HeaderView return false; } else if (position < getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount()) { position = position - getHeaderViewsCount(); // HeaderItem (negative positions will throw an // IndexOutOfBoundsException) int numHeaders = getHeaderItemsCount(); if (position < numHeaders) { return mHeaderItemInfo.get(position).isSelectable; } // Adapter final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.isEnabled(adjPosition); } } // FooterItem (off-limits positions will throw an // IndexOutOfBoundsException) return mFooterItemInfo.get(adjPosition - adapterCount).isSelectable; } else { if (position < getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount() + unusedPositionCount) return false; // FooterView return false; } } @Override public Object getItem(int position) { if (position < getHeaderViewsCount()) { // HeaderView return null; } else if (position < getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount()) { position = position - getHeaderViewsCount(); // HeaderItem (negative positions will throw an // IndexOutOfBoundsException) int numHeaders = getHeaderItemsCount(); if (position < numHeaders) { return mHeaderItemInfo.get(position).data; } // Adapter final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.getItem(adjPosition); } } // FooterItem (off-limits positions will throw an // IndexOutOfBoundsException) return mFooterItemInfo.get(adjPosition - adapterCount).data; } else { // FooterView return null; } } @Override public long getItemId(int position) { int numHeaders = getHeaderItemsCount() + getHeaderViewsCount(); if (mAdapter != null && position >= numHeaders) { int adjPosition = position - numHeaders; int adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.getItemId(adjPosition); } } return -1; } @Override public boolean hasStableIds() { return mAdapter != null && mAdapter.hasStableIds(); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (position < getHeaderViewsCount()) { // HeaderView return position % mNumColumns == 0 ? getHeader(position) : getHideView(getHeader(position)); } else if (position < getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount()) { position = position - getHeaderViewsCount(); // HeaderItem (negative positions will throw an // IndexOutOfBoundsException) int numHeaders = getHeaderItemsCount(); if (position < numHeaders) { return mHeaderItemInfo.get(position).view; } // Adapter final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.getView(adjPosition, convertView, parent); } } // FooterItem (off-limits positions will throw an // IndexOutOfBoundsException) return mFooterItemInfo.get(adjPosition - adapterCount).view; } else if (position < getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount() + unusedPositionCount) { View simple = null; if (mFooterItemInfo.size() > 0) simple = mFooterItemInfo.get(mFooterItemInfo.size() - 1).view; else if (mAdapter != null && mAdapter.getCount() > 0) simple = mAdapter.getView(mAdapter.getCount() - 1, null, parent); else if (mHeaderItemInfo.size() > 0) simple = mHeaderItemInfo.get(mHeaderItemInfo.size() - 1).view; else if (mHeaderViews.size() > 0) simple = mHeaderViews.get(mHeaderViews.size() - 1); //noinspection ConstantConditions return getHideView(simple); } else { // FooterView return position % mNumColumns == 0 ? getFooter(position) : getHideView(getFooter(position)); } } @Override public int getItemViewType(int position) { int numHeaders = getHeaderItemsCount() + getHeaderViewsCount(); if (mAdapter != null && position >= numHeaders) { int adjPosition = position - numHeaders; int adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.getItemViewType(adjPosition); } } return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; } @Override public int getViewTypeCount() { if (mAdapter != null) { return mAdapter.getViewTypeCount(); } return 1; } @Override public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); if (mAdapter != null) { mAdapter.registerDataSetObserver(observer); } } @Override public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); if (mAdapter != null) { mAdapter.unregisterDataSetObserver(observer); } } @Override public Filter getFilter() { if (mIsFilterable) { return ((Filterable) mAdapter).getFilter(); } return null; } @Override public ListAdapter getWrappedAdapter() { return mAdapter; } /** * 获取实际子项数目 * * @return 实际子项数目 */ public int getItemCount() { if (mAdapter != null) { return mAdapter.getCount(); } else { return 0; } } /** * 获取子项的第一项position(包含HeaderItem与FooterItem,不包含HeaderView及其占用的空白View) * 原Adapter无Item并且也无HeaderItem与FooterItem时,返回 AdapterView.INVALID_POSITION * * @return 子项的第一项position */ public int getFirstItemPosition() { // adapter有值或者有HeaderItem if ((mAdapter != null && mAdapter.getCount() > 0) || getHeaderItemsCount() > 0 || getFooterItemsCount() > 0) return getHeaderViewsCount(); else return AdapterView.INVALID_POSITION; } /** * 获取子项的最后项position(包含HeaderItem与FooterItem,不包含FooterView及其占用的空白View) * 原Adapter无Item并且也无HeaderItem与FooterItem时,返回 AdapterView.INVALID_POSITION * * @return 获取子项的最后项position */ public int getLastItemPosition() { // adapter有值或者有HeaderItem if ((mAdapter != null && mAdapter.getCount() > 0) || getHeaderItemsCount() > 0 || getFooterItemsCount() > 0) return getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount() - 1; else return AdapterView.INVALID_POSITION; } /** * 获取末个HeaderItem位置 * 无HeaderItem则返回 AdapterView.INVALID_POSITION * * @return 末个HeaderItem位置 */ @SuppressWarnings("unused") public int getLastHeaderItemPosition() { // Header if (mHeaderItemInfo.size() > 0) { return getHeaderViewsCount() + getHeaderItemsCount() - 1; } else { return AdapterView.INVALID_POSITION; } } /** * 获取实际子项的第一项position * 原Adapter无Item时,返回 AdapterView.INVALID_POSITION * * @return 实际子项的第一项position */ public int getFirstWrappedAdapterItemPosition() { // adapter有值 if (mAdapter != null && mAdapter.getCount() > 0) { return getHeaderViewsCount() + getHeaderItemsCount(); } else { return AdapterView.INVALID_POSITION; } } /** * 获取首个FooterItem位置 * 无FooterItem则返回 AdapterView.INVALID_POSITION * * @return 首个FooterItem位置 */ public int getFirstFooterItemPosition() { // Footer if (mFooterItemInfo.size() > 0) { return getHeaderViewsCount() + getHeaderItemsCount() + getItemCount(); } else { return AdapterView.INVALID_POSITION; } } /** * 获取头Item的集合位置 * 返回 AdapterView.INVALID_POSITION 表明其对应的不是头Item * * @param position 位置 * @return 头Item的集合位置 */ @SuppressWarnings("unused") public int positionToHeaderItemIndex(int position) { final int firstFooterItem = getFirstFooterItemPosition(); if (getHeaderItemsCount() > 0 && firstFooterItem > position && position <= getHeaderViewsCount()) { return position - getHeaderViewsCount(); } else { return AdapterView.INVALID_POSITION; } } /** * 获取尾Item的集合位置 * 返回 AdapterView.INVALID_POSITION 表明其对应的不是尾Item * * @param position 位置 * @return 尾Item的集合位置 */ @SuppressWarnings("unused") public int positionToFooterItemIndex(int position) { final int firstFooterItem = getFirstFooterItemPosition(); if (getFooterItemsCount() > 0 && firstFooterItem >= position && position < firstFooterItem + getFooterItemsCount()) { return position - firstFooterItem; } else { return AdapterView.INVALID_POSITION; } } /** * 获取HeaderView的集合位置(HeaderView占用的空白位置也算作HeaderView) * 返回 AdapterView.INVALID_POSITION 表明其对应的不是HeaderView * * @param position 位置 * @return HeaderView的集合位置 */ public int positionToHeaderViewIndex(int position) { if (getHeaderViewsCount() > 0 && position < getHeaderViewsCount()) { return position / mNumColumns; } else { return AdapterView.INVALID_POSITION; } } /** * 获取FooterView的集合位置(FooterView占用的空白位置也算作FooterView) * 返回 AdapterView.INVALID_POSITION 表明其对应的不是FooterView * * @param position 位置 * @return FooterView的集合位置 */ public int positionToFooterViewIndex(int position) { int startPosition = getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount() + unusedPositionCount; if (getFooterViewsCount() > 0 && position >= startPosition) { return (position - startPosition) / mNumColumns; } else { return AdapterView.INVALID_POSITION; } } /** * 获取包含HeaderView占用的位置,实际为空白View充填 * * @return 包含HeaderView占用的位置 */ public int getHeaderViewsCount() { return mHeaderViews.size() * mNumColumns; } /** * 获取包含FooterView占用的位置以及最后一行不足一行的几个剩余位置,实际为空白View充填 * * @return 包含FooterView占用的位置以及最后一行不足一行的几个剩余位置 */ public int getFooterViewsCount() { int startPosition = getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount(); int position = startPosition; unusedPositionCount = 0; for (int i = 0; i < mFooterViews.size(); position++) { if (position % mNumColumns == 0) { i++; } if (i == 0) { unusedPositionCount++; } } return position - startPosition; } /** * 获取无效Position * * @return 无效Position */ @SuppressWarnings("unused") public int getUnusedPositionCount() { return unusedPositionCount; } /** * 获取 position 对应的类型 * * @param position 位置 * @return 对应的类型 */ public PositionType getPositionType(int position) { if (position < getHeaderViewsCount() + getHeaderItemsCount()) { if (position % mNumColumns == 0) return PositionType.HEADER_VIEW; else return PositionType.HEADER_EMPTY; } else if (position >= getHeaderViewsCount() + getHeaderItemsCount() + getItemCount() + getFooterItemsCount()) { if (position % mNumColumns == 0) return PositionType.FOOTER_VIEW; else return PositionType.FOOTER_EMPTY; } else { if (position < getHeaderViewsCount() + getHeaderItemsCount()) return PositionType.HEADER_ITEM; else if (position >= getHeaderViewsCount() + getHeaderItemsCount() + getItemCount()) return PositionType.FOOTER_ITEM; else return PositionType.NORMAL; } } public int getNumColumns() { return mNumColumns; } /** * 设置列数 * 列数大于0才允许设置 * * @param numColumns 列数 */ public void setNumColumns(int numColumns) { if (mNumColumns != numColumns && numColumns > 0) { mNumColumns = numColumns; notifyDataSetChanged(); } } private View getHeader(int position) { View header = null; for (int i = 0; i <= mHeaderViews.size(); i++) { if (position < (i + 1) * mNumColumns) { header = mHeaderViews.get(i); break; } } return header; } private View getFooter(int position) { View footer = null; position = position - getHeaderViewsCount() - getHeaderItemsCount() - getItemCount() - getFooterItemsCount() - unusedPositionCount; for (int i = 0; i <= mFooterViews.size(); i++) { if (position < (i + 1) * mNumColumns) { footer = mFooterViews.get(i); break; } } return footer; } private View getHideView(View simple) { int height = simple.getMeasuredHeight(); if (height == 0) { ViewGroup.LayoutParams p = simple.getLayoutParams(); if (!(p instanceof AbsListView.LayoutParams)) { p = new AbsListView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); } simple.setLayoutParams(p); simple.measure( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); height = simple.getMeasuredHeight(); } LinearLayout lyt = new LinearLayout(simple.getContext()); View view = new View(simple.getContext()); lyt.addView(view); ViewGroup.LayoutParams lp = view.getLayoutParams(); lp.height = height; view.setLayoutParams(lp); return lyt; } public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); } @SuppressWarnings("unused") public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } public enum PositionType { HEADER_EMPTY, HEADER_VIEW, HEADER_ITEM, NORMAL, FOOTER_ITEM, FOOTER_VIEW, FOOTER_EMPTY } }