Android带FooterView的GridView

   最近使用GridView,希望能像ListView一样可以添加footerView,来实现滚动到底部自动加载更多的功能。但是GridView没提供添加FooterView的方法,只能自己实现了。参考了HeaderGridView的实现,本以为添加FooterView会很简单,但发现还是有很多不同的地方。

  给GridView的添加Footer的原理就是,假如说我们的GridView每行显示3列,也就是说每一行有3个ItemView,但我们的FooterVIew要占整个一行怎么办,那我们就把FooterView所在的一行给它补上两个view,然后把这两个View的高度设置成FooterView的高度,不然的话,如果高度小了,FooterView会显示不全,并且把这两个补全的View的Visibile设置成INVISIBLE,这样FooterView就能完全的显示在一整行了。如图:

   现在我们要做的有两件事情,一个是获取FooterView的高度,因为我们要给两个补全的view设置和FooterView统一的高度,另一件事就是我们要把FooterView 的宽度设置成GridView整行的宽度。代码如下:

	public void addFooterView(View v, Object data, boolean isSelectable) {
		ListAdapter adapter = getAdapter();
		if (adapter != null && !(adapter instanceof FooterViewGridAdapter)) {
			throw new IllegalStateException("Cannot add header view to grid -- setAdapter has already been called.");
		}
		FixedViewInfo info = new FixedViewInfo();
		FrameLayout fl = new FullWidthFixedViewLayout(getContext());
		fl.addView(v);
		info.view = v;
		info.viewContainer = fl;
		info.data = data;
		info.isSelectable = isSelectable;
		mFooterViewInfos.add(info);
		if (adapter != null) {
			((FooterViewGridAdapter) adapter).notifyDataSetChanged();
		}
	}

这是我们添加FooterView的方法,其中有个FullWidthFixedViewLayout的ViewGroup,这是一个内部类,重写了它的onMeasure方法,把它的宽度设置成GridView的宽度,该类实现如下:

	private class FullWidthFixedViewLayout extends FrameLayout {
		public FullWidthFixedViewLayout(Context context) {
			super(context);
		}

		@Override
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
			int targetWidth = FooterGridView.this.getMeasuredWidth() - FooterGridView.this.getPaddingLeft() - FooterGridView.this.getPaddingRight();
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.getMode(widthMeasureSpec));
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}


    上面这种情况是iitemView正好占满一行的情况,如果itemView没有占满一行怎么办,那么我们也要把空缺的列给补上,并且隐藏掉。同样也需要把它的高度设置成itemView的高度。如图:

获取itemView的高度方法如下:

	private int getRowHeight() {
		int childHeight = 0;
		ListAdapter lAdapter = getAdapter();
		if (lAdapter != null) {
			int mItemCount = lAdapter instanceof FooterViewGridAdapter ? ((FooterViewGridAdapter) lAdapter).getWrappedAdapter().getCount() : lAdapter
					.getCount();
			final int count = mItemCount;
			if (count > 0) {
				View child = getAdapter().getView(count - 1, null, this);
				AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
				if (p == null) {
					p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
					child.setLayoutParams(p);
				}
				int childHeightSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
				child.measure(0, childHeightSpec);

				childHeight = child.getMeasuredHeight();
			}
		}
		return childHeight;
	}

这里的FooterViewGridAdapter是我们自定义的内部Adapter,它实现了WrapperListAdapter,如果设置了FooterView,我们就用它来包装外面传过来的Adapter.完整代码如下:

import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.WrapperListAdapter;

public class FooterGridView extends GridView {

	private static final String TAG = "FooterGridView";

	/**
	 * A class that represents a fixed view in a list, for example a header at
	 * the top or a footer at the bottom.
	 */
	private static class FixedViewInfo {
		/** The view to add to the grid */
		public View view;
		public ViewGroup viewContainer;

		/**
		 * The data backing the view. This is returned from
		 * {@link ListAdapter#getItem(int)}.
		 */
		public Object data;
		/** <code>true</code> if the fixed view should be selectable in the grid */
		public boolean isSelectable;
	}

	private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>();

	private void initFooterGridView() {
		super.setClipChildren(false);
	}

	public FooterGridView(Context context) {
		super(context);
		initFooterGridView();
	}

	public FooterGridView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initFooterGridView();
	}

	public FooterGridView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initFooterGridView();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		ListAdapter adapter = getAdapter();
		if (adapter != null && adapter instanceof FooterViewGridAdapter) {
			FooterViewGridAdapter fAdapter = (FooterViewGridAdapter) adapter;
			fAdapter.setNumColumns(getNumColumns());
			fAdapter.setColumnWidth(getColumnWidth());
			fAdapter.setRowHeight(getRowHeight());
		}
	}

	@Override
	public void setClipChildren(boolean clipChildren) {
		// Ignore, since the header rows depend on not being clipped
	}

	/**
	 * Add a fixed view to appear at the top of the grid. If addFooterView is
	 * called more than once, the views will appear in the order they were
	 * added. Views added using this call can take focus if they want.
	 * <p>
	 * NOTE: Call this before calling setAdapter. This is so FooterGridView can
	 * wrap the supplied cursor with one that will also account for header
	 * views.
	 * 
	 * @param v
	 *            The view to add.
	 * @param data
	 *            Data to associate with this view
	 * @param isSelectable
	 *            whether the item is selectable
	 */
	public void addFooterView(View v, Object data, boolean isSelectable) {
		ListAdapter adapter = getAdapter();
		if (adapter != null && !(adapter instanceof FooterViewGridAdapter)) {
			throw new IllegalStateException("Cannot add header view to grid -- setAdapter has already been called.");
		}
		FixedViewInfo info = new FixedViewInfo();
		FrameLayout fl = new FullWidthFixedViewLayout(getContext());
		fl.addView(v);
		info.view = v;
		info.viewContainer = fl;
		info.data = data;
		info.isSelectable = isSelectable;
		mFooterViewInfos.add(info);
		if (adapter != null) {
			((FooterViewGridAdapter) adapter).notifyDataSetChanged();
		}
	}

	/**
	 * Add a fixed view to appear at the top of the grid. If addFooterView is
	 * called more than once, the views will appear in the order they were
	 * added. Views added using this call can take focus if they want.
	 * <p>
	 * NOTE: Call this before calling setAdapter. This is so FooterGridView can
	 * wrap the supplied cursor with one that will also account for header
	 * views.
	 * 
	 * @param v
	 *            The view to add.
	 */
	public void addFooterView(View v) {
		addFooterView(v, null, true);
	}

	public int getFooterViewCount() {
		return mFooterViewInfos.size();
	}

	/**
	 * Removes a previously-added header view.
	 * 
	 * @param v
	 *            The view to remove
	 * @return true if the view was removed, false if the view was not a header
	 *         view
	 */
	public boolean removeFooterView(View v) {
		if (mFooterViewInfos.size() > 0) {
			boolean result = false;
			ListAdapter adapter = getAdapter();
			if (adapter != null && ((FooterViewGridAdapter) adapter).removeFooter(v)) {
				result = true;
			}
			removeFixedViewInfo(v, mFooterViewInfos);
			return result;
		}
		return false;
	}

	private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
		int len = where.size();
		for (int i = 0; i < len; ++i) {
			FixedViewInfo info = where.get(i);
			if (info.view == v) {
				where.remove(i);
				break;
			}
		}
	}

	/**
	 * 设置Adapter的方法,如果设置了footerView,就用FooterViewGridAdapter来封装。
	 */
	@Override
	public void setAdapter(ListAdapter adapter) {
		if (mFooterViewInfos.size() > 0) {
			FooterViewGridAdapter hadapter = new FooterViewGridAdapter(mFooterViewInfos, adapter);
			int numColumns = getNumColumns();
			if (numColumns > 1) {
				hadapter.setNumColumns(numColumns);
			}
			super.setAdapter(hadapter);
		} else {
			super.setAdapter(adapter);
		}
	}

	// 获取每行高度的方法
	private int getRowHeight() {
		int childHeight = 0;
		ListAdapter lAdapter = getAdapter();
		if (lAdapter != null) {
			int mItemCount = lAdapter instanceof FooterViewGridAdapter && ((FooterViewGridAdapter) lAdapter).getWrappedAdapter() != null ? ((FooterViewGridAdapter) lAdapter).getWrappedAdapter().getCount() : 0;
			final int count = mItemCount;
			if (count > 0) {
				View child = getAdapter().getView(count - 1, null, this);
				AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
				if (p == null) {
					p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
					child.setLayoutParams(p);
				}
				int childHeightSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
				child.measure(0, childHeightSpec);

				childHeight = child.getMeasuredHeight();
			}
		}
		return childHeight;
	}

	private class FullWidthFixedViewLayout extends FrameLayout {
		public FullWidthFixedViewLayout(Context context) {
			super(context);
		}

		// 重写onMeasure 使footerView的宽度占整行GridView的宽度
		@Override
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
			int targetWidth = FooterGridView.this.getMeasuredWidth() - FooterGridView.this.getPaddingLeft() - FooterGridView.this.getPaddingRight();
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.getMode(widthMeasureSpec));
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * ListAdapter used when a FooterGridView has header views. This ListAdapter
	 * wraps another one and also keeps track of the header views and their
	 * associated data objects.
	 * <p>
	 * This is intended as a base class; you will probably not need to use this
	 * class directly in your own code.
	 */
	private static class FooterViewGridAdapter implements WrapperListAdapter, Filterable {
		// This is used to notify the container of updates relating to number of
		// columns
		// or headers changing, which changes the number of placeholders needed
		private final DataSetObservable mDataSetObservable = new DataSetObservable();
		private final ListAdapter mAdapter;
		private int mNumColumns = 1;
		private int mRowHeight;
		private int mColumnWidth;
		// This ArrayList is assumed to NOT be null.
		ArrayList<FixedViewInfo> mFooterViewInfos;
		boolean mAreAllFixedViewsSelectable;
		private final boolean mIsFilterable;

		public FooterViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ListAdapter adapter) {
			mAdapter = adapter;
			mIsFilterable = adapter instanceof Filterable;
			if (headerViewInfos == null) {
				throw new IllegalArgumentException("headerViewInfos cannot be null");
			}
			mFooterViewInfos = headerViewInfos;
			mAreAllFixedViewsSelectable = areAllListInfosSelectable(mFooterViewInfos);
		}

		public int getFootersCount() {
			return mFooterViewInfos.size();
		}

		@Override
		public boolean isEmpty() {
			return (mAdapter == null || mAdapter.isEmpty()) && getFootersCount() == 0;
		}

		public void setNumColumns(int numColumns) {
			if (numColumns < 1) {
				throw new IllegalArgumentException("Number of columns must be 1 or more");
			}
			if (mNumColumns != numColumns) {
				mNumColumns = numColumns;
				notifyDataSetChanged();
			}
		}

		public void setColumnWidth(int columnWidth) {
			this.mColumnWidth = columnWidth;
		}

		public void setRowHeight(int rowHeight) {
			this.mRowHeight = rowHeight;
		}

		private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
			if (infos != null) {
				for (FixedViewInfo info : infos) {
					if (!info.isSelectable) {
						return false;
					}
				}
			}
			return true;
		}

		public boolean removeFooter(View v) {
			for (int i = 0; i < mFooterViewInfos.size(); i++) {
				FixedViewInfo info = mFooterViewInfos.get(i);
				if (info.view == v) {
					mFooterViewInfos.remove(i);
					mAreAllFixedViewsSelectable = areAllListInfosSelectable(mFooterViewInfos);
					mDataSetObservable.notifyChanged();
					return true;
				}
			}
			return false;
		}

		/**
		 * 如果设置了footerView而adapter为空的话,则count的数量等于footerView的个数 乘以 GridView每行的列数
		 * 如果adapter不为空的话,则count的数量等于footerView的个数 乘以 GridView每行的列数 加上
		 * adapter的count 加上补全View的个数
		 */
		@Override
		public int getCount() {
			if (mAdapter != null) {
				if (mAdapter.getCount() % mNumColumns == 0) {
					return getFootersCount() * mNumColumns + mAdapter.getCount();
				} else {
					return getFootersCount() * mNumColumns + mAdapter.getCount() + (mNumColumns - mAdapter.getCount() % mNumColumns);
				}
			} else {
				return getFootersCount() * mNumColumns;
			}
		}

		@Override
		public boolean areAllItemsEnabled() {
			if (mAdapter != null) {
				return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
			} else {
				return true;
			}
		}

		@Override
		public boolean isEnabled(int position) {
			if (mAdapter != null) {
				int adapterCount = mAdapter.getCount();
				if (position < adapterCount) {
					return mAdapter.isEnabled(position);
				} else {
					if (adapterCount % mNumColumns == 0) {
						return mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns).isSelectable;
					} else {
						return mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns - 1).isSelectable;
					}
				}
				
			}
			return position % mNumColumns == 0 && mFooterViewInfos.get(position / mNumColumns).isSelectable;
		}

		@Override
		public Object getItem(int position) {
			if (mAdapter != null) {
				int adapterCount = mAdapter.getCount();
				if (position < adapterCount) {
					return mAdapter.getItem(position);
				} else {
					if (adapterCount % mNumColumns == 0) {
						return mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns).data;
					} else {
						return mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns - 1).data;
					}
				}
			}
			return position % mNumColumns == 0 ? mFooterViewInfos.get(position / mNumColumns).data : null;
		}

		@Override
		public long getItemId(int position) {
			if (mAdapter != null) {
				return mAdapter.getItemId(position);
			}
			return -1;
		}

		@Override
		public boolean hasStableIds() {
			if (mAdapter != null) {
				return mAdapter.hasStableIds();
			}
			return false;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			if (mAdapter != null) {
				int adapterCount = mAdapter.getCount();
				if (position < adapterCount) {// 如果position小于adapterCount则只返回itemView
					return mAdapter.getView(position, convertView, parent);
				} else if (adapterCount % mNumColumns == 0) {// 如果adapterCount余每行显示的列数等于0的话,则说明itemView正好占满一行,就不需要补全View
					View footerViewContainer = mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns).viewContainer;
					if (position % mNumColumns == 0) {// 最后一行的第一个应该是footerView
						return footerViewContainer;
					} else {// 最后一行为FooterView 补全的View
						if (convertView == null) {
							convertView = new View(parent.getContext());
						}
						convertView.setVisibility(View.INVISIBLE);
						convertView.setMinimumHeight(footerViewContainer.getHeight());
						return convertView;
					}
				} else {// 这是itemView没有占满一行的情况,则需要补全
					int vacantCount = mNumColumns - adapterCount % mNumColumns;// 补全View的个数
					if (position < (adapterCount + vacantCount)) {
						if (convertView == null) {
							convertView = new View(parent.getContext());
						}
						convertView.setVisibility(View.INVISIBLE);
						convertView.setMinimumWidth(mColumnWidth);
						convertView.setMinimumHeight(mRowHeight);
						return convertView;
					} else {
						View footerViewContainer = mFooterViewInfos.get(position / mNumColumns - adapterCount / mNumColumns - 1).viewContainer;
						if (position % mNumColumns == 0) {
							return footerViewContainer;
						} else {
							if (convertView == null) {
								convertView = new View(parent.getContext());
							}
							convertView.setVisibility(View.INVISIBLE);
							convertView.setMinimumHeight(footerViewContainer.getHeight());
							return convertView;
						}
					}
				}
			} else {// 如果外部adapter为空的话,则只返回FooterView和补全的View
				View footerViewContainer = mFooterViewInfos.get(position / mNumColumns).viewContainer;
				if (position % mNumColumns == 0) {
					return footerViewContainer;
				} else {
					if (convertView == null) {
						convertView = new View(parent.getContext());
					}
					convertView.setVisibility(View.INVISIBLE);
					convertView.setMinimumHeight(footerViewContainer.getHeight());
					return convertView;
				}
			}
		}

		@Override
		public int getItemViewType(int position) {

			if (mAdapter != null) {
				int adapterCount = mAdapter.getCount();
				if (position < adapterCount) {
					return mAdapter.getItemViewType(position);
				} else if (position % mNumColumns == 0) {
					return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
				} else {
					int vacantCount = mNumColumns - adapterCount % mNumColumns;
					if (position < (adapterCount + vacantCount)) {
						return mAdapter.getViewTypeCount() + 1;
					} else {
						return mAdapter.getViewTypeCount();
					}

				}
			}

			return position % mNumColumns == 0 ? AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER : 1;
		}

		@Override
		public int getViewTypeCount() {
			if (mAdapter != null) {
				return mAdapter.getViewTypeCount() + 2;
			}
			return 2;
		}

		@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;
		}

		public void notifyDataSetChanged() {
			mDataSetObservable.notifyChanged();
		}
	}
}




 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值