为GridView添加头布局

实现这个功能一般有两种思路,一种思路是使用ScrollView+GridView,第二种思路是使用ListView来实现GridView的效果。

第一种思路的具体实现是把HeaderView和GridView都放到ScrollView里面,这里要解决的问题是ScrollView和GridView滑动手势的冲突问题,解决办法是让GridView充满ScrollView,不让GridView滑动而只让ScrollView滑动。具题做法是重载GridView的onMeasure()方法。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MyGridView extends GridView {   
  2.     public MyGridView(Context context, AttributeSet attrs) {   
  3.         super(context, attrs);   
  4.     }   
  5.   
  6.     public MyGridView(Context context) {   
  7.         super(context);   
  8.     }   
  9.   
  10.     public MyGridView(Context context, AttributeSet attrs, int defStyle) {   
  11.         super(context, attrs, defStyle);   
  12.     }   
  13.   
  14.     @Override   
  15.     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
  16.   
  17.         int expandSpec = MeasureSpec.makeMeasureSpec(   
  18.                 Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);   
  19.         super.onMeasure(widthMeasureSpec, expandSpec);   
  20.     }   
  21. }   

这种方法的不足是GridView中的View没有复用,如果内容较多将比较消耗内存。

第二种思路的具体实现是使用ListView的addHeaderView()来添加HeaderView,而ListView的每一行都放一个LinearLayout来保存一行的item,这里要注意的是item的个数和ListView行数的关系。

后来在StackOverFlow上又看到了第三中方法。原来Google已经用GridView实现了,而且很巧妙,下面把代码贴出来

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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 com.android.photos.views;  
  17. import android.content.Context;  
  18. import android.database.DataSetObservable;  
  19. import android.database.DataSetObserver;  
  20. import android.util.AttributeSet;  
  21. import android.view.View;  
  22. import android.view.ViewGroup;  
  23. import android.widget.AdapterView;  
  24. import android.widget.Filter;  
  25. import android.widget.Filterable;  
  26. import android.widget.FrameLayout;  
  27. import android.widget.GridView;  
  28. import android.widget.ListAdapter;  
  29. import android.widget.WrapperListAdapter;  
  30. import java.util.ArrayList;  
  31. /** 
  32.  * A {@link GridView} that supports adding header rows in a 
  33.  * very similar way to {@link ListView}. 
  34.  * See {@link HeaderGridView#addHeaderView(View, Object, boolean)} 
  35.  */  
  36. public class HeaderGridView extends GridView {  
  37.     private static final String TAG = "HeaderGridView";  
  38.     /** 
  39.      * A class that represents a fixed view in a list, for example a header at the top 
  40.      * or a footer at the bottom. 
  41.      */  
  42.     private static class FixedViewInfo {  
  43.         /** The view to add to the grid */  
  44.         public View view;  
  45.         public ViewGroup viewContainer;  
  46.         /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */  
  47.         public Object data;  
  48.         /** <code>true</code> if the fixed view should be selectable in the grid */  
  49.         public boolean isSelectable;  
  50.     }  
  51.     private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();  
  52.     private void initHeaderGridView() {  
  53.         super.setClipChildren(false);  
  54.     }  
  55.     public HeaderGridView(Context context) {  
  56.         super(context);  
  57.         initHeaderGridView();  
  58.     }  
  59.     public HeaderGridView(Context context, AttributeSet attrs) {  
  60.         super(context, attrs);  
  61.         initHeaderGridView();  
  62.     }  
  63.     public HeaderGridView(Context context, AttributeSet attrs, int defStyle) {  
  64.         super(context, attrs, defStyle);  
  65.         initHeaderGridView();  
  66.     }  
  67.     @Override  
  68.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  69.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  70.         ListAdapter adapter = getAdapter();  
  71.         if (adapter != null && adapter instanceof HeaderViewGridAdapter) {  
  72.             ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumns());  
  73.         }  
  74.     }  
  75.     @Override  
  76.     public void setClipChildren(boolean clipChildren) {  
  77.        // Ignore, since the header rows depend on not being clipped  
  78.     }  
  79.     /** 
  80.      * Add a fixed view to appear at the top of the grid. If addHeaderView is 
  81.      * called more than once, the views will appear in the order they were 
  82.      * added. Views added using this call can take focus if they want. 
  83.      * <p> 
  84.      * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap 
  85.      * the supplied cursor with one that will also account for header views. 
  86.      * 
  87.      * @param v The view to add. 
  88.      * @param data Data to associate with this view 
  89.      * @param isSelectable whether the item is selectable 
  90.      */  
  91.     public void addHeaderView(View v, Object data, boolean isSelectable) {  
  92.         ListAdapter adapter = getAdapter();  
  93.         if (adapter != null && ! (adapter instanceof HeaderViewGridAdapter)) {  
  94.             throw new IllegalStateException(  
  95.                     "Cannot add header view to grid -- setAdapter has already been called.");  
  96.         }  
  97.         FixedViewInfo info = new FixedViewInfo();  
  98.         FrameLayout fl = new FullWidthFixedViewLayout(getContext());  
  99.         fl.addView(v);  
  100.         info.view = v;  
  101.         info.viewContainer = fl;  
  102.         info.data = data;  
  103.         info.isSelectable = isSelectable;  
  104.         mHeaderViewInfos.add(info);  
  105.         // in the case of re-adding a header view, or adding one later on,  
  106.         // we need to notify the observer  
  107.         if (adapter != null) {  
  108.             ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();  
  109.         }  
  110.     }  
  111.     /** 
  112.      * Add a fixed view to appear at the top of the grid. If addHeaderView is 
  113.      * called more than once, the views will appear in the order they were 
  114.      * added. Views added using this call can take focus if they want. 
  115.      * <p> 
  116.      * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap 
  117.      * the supplied cursor with one that will also account for header views. 
  118.      * 
  119.      * @param v The view to add. 
  120.      */  
  121.     public void addHeaderView(View v) {  
  122.         addHeaderView(v, nulltrue);  
  123.     }  
  124.     public int getHeaderViewCount() {  
  125.         return mHeaderViewInfos.size();  
  126.     }  
  127.     /** 
  128.      * Removes a previously-added header view. 
  129.      * 
  130.      * @param v The view to remove 
  131.      * @return true if the view was removed, false if the view was not a header 
  132.      *         view 
  133.      */  
  134.     public boolean removeHeaderView(View v) {  
  135.         if (mHeaderViewInfos.size() > 0) {  
  136.             boolean result = false;  
  137.             ListAdapter adapter = getAdapter();  
  138.             if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {  
  139.                 result = true;  
  140.             }  
  141.             removeFixedViewInfo(v, mHeaderViewInfos);  
  142.             return result;  
  143.         }  
  144.         return false;  
  145.     }  
  146.     private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {  
  147.         int len = where.size();  
  148.         for (int i = 0; i < len; ++i) {  
  149.             FixedViewInfo info = where.get(i);  
  150.             if (info.view == v) {  
  151.                 where.remove(i);  
  152.                 break;  
  153.             }  
  154.         }  
  155.     }  
  156.     @Override  
  157.     public void setAdapter(ListAdapter adapter) {  
  158.         if (mHeaderViewInfos.size() > 0) {  
  159.             HeaderViewGridAdapter hadapter = new HeaderViewGridAdapter(mHeaderViewInfos, adapter);  
  160.             int numColumns = getNumColumns();  
  161.             if (numColumns > 1) {  
  162.                 hadapter.setNumColumns(numColumns);  
  163.             }  
  164.             super.setAdapter(hadapter);  
  165.         } else {  
  166.             super.setAdapter(adapter);  
  167.         }  
  168.     }  
  169.     private class FullWidthFixedViewLayout extends FrameLayout {  
  170.         public FullWidthFixedViewLayout(Context context) {  
  171.             super(context);  
  172.         }  
  173.         @Override  
  174.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  175.             int targetWidth = HeaderGridView.this.getMeasuredWidth()  
  176.                     - HeaderGridView.this.getPaddingLeft()  
  177.                     - HeaderGridView.this.getPaddingRight();  
  178.             widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,  
  179.                     MeasureSpec.getMode(widthMeasureSpec));  
  180.             super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  181.         }  
  182.     }  
  183.     /** 
  184.      * ListAdapter used when a HeaderGridView has header views. This ListAdapter 
  185.      * wraps another one and also keeps track of the header views and their 
  186.      * associated data objects. 
  187.      *<p>This is intended as a base class; you will probably not need to 
  188.      * use this class directly in your own code. 
  189.      */  
  190.     private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable {  
  191.         // This is used to notify the container of updates relating to number of columns  
  192.         // or headers changing, which changes the number of placeholders needed  
  193.         private final DataSetObservable mDataSetObservable = new DataSetObservable();  
  194.         private final ListAdapter mAdapter;  
  195.         private int mNumColumns = 1;  
  196.         // This ArrayList is assumed to NOT be null.  
  197.         ArrayList<FixedViewInfo> mHeaderViewInfos;  
  198.         boolean mAreAllFixedViewsSelectable;  
  199.         private final boolean mIsFilterable;  
  200.         public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ListAdapter adapter) {  
  201.             mAdapter = adapter;  
  202.             mIsFilterable = adapter instanceof Filterable;  
  203.             if (headerViewInfos == null) {  
  204.                 throw new IllegalArgumentException("headerViewInfos cannot be null");  
  205.             }  
  206.             mHeaderViewInfos = headerViewInfos;  
  207.             mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);  
  208.         }  
  209.         public int getHeadersCount() {  
  210.             return mHeaderViewInfos.size();  
  211.         }  
  212.         @Override  
  213.         public boolean isEmpty() {  
  214.             return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0;  
  215.         }  
  216.         public void setNumColumns(int numColumns) {  
  217.             if (numColumns < 1) {  
  218.                 throw new IllegalArgumentException("Number of columns must be 1 or more");  
  219.             }  
  220.             if (mNumColumns != numColumns) {  
  221.                 mNumColumns = numColumns;  
  222.                 notifyDataSetChanged();  
  223.             }  
  224.         }  
  225.         private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {  
  226.             if (infos != null) {  
  227.                 for (FixedViewInfo info : infos) {  
  228.                     if (!info.isSelectable) {  
  229.                         return false;  
  230.                     }  
  231.                 }  
  232.             }  
  233.             return true;  
  234.         }  
  235.         public boolean removeHeader(View v) {  
  236.             for (int i = 0; i < mHeaderViewInfos.size(); i++) {  
  237.                 FixedViewInfo info = mHeaderViewInfos.get(i);  
  238.                 if (info.view == v) {  
  239.                     mHeaderViewInfos.remove(i);  
  240.                     mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);  
  241.                     mDataSetObservable.notifyChanged();  
  242.                     return true;  
  243.                 }  
  244.             }  
  245.             return false;  
  246.         }  
  247.         @Override  
  248.         public int getCount() {  
  249.             if (mAdapter != null) {  
  250.                 return getHeadersCount() * mNumColumns + mAdapter.getCount();  
  251.             } else {  
  252.                 return getHeadersCount() * mNumColumns;  
  253.             }  
  254.         }  
  255.         @Override  
  256.         public boolean areAllItemsEnabled() {  
  257.             if (mAdapter != null) {  
  258.                 return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();  
  259.             } else {  
  260.                 return true;  
  261.             }  
  262.         }  
  263.         @Override  
  264.         public boolean isEnabled(int position) {  
  265.             // Header (negative positions will throw an ArrayIndexOutOfBoundsException)  
  266.             int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;  
  267.             if (position < numHeadersAndPlaceholders) {  
  268.                 return (position % mNumColumns == 0)  
  269.                         && mHeaderViewInfos.get(position / mNumColumns).isSelectable;  
  270.             }  
  271.             // Adapter  
  272.             final int adjPosition = position - numHeadersAndPlaceholders;  
  273.             int adapterCount = 0;  
  274.             if (mAdapter != null) {  
  275.                 adapterCount = mAdapter.getCount();  
  276.                 if (adjPosition < adapterCount) {  
  277.                     return mAdapter.isEnabled(adjPosition);  
  278.                 }  
  279.             }  
  280.             throw new ArrayIndexOutOfBoundsException(position);  
  281.         }  
  282.         @Override  
  283.         public Object getItem(int position) {  
  284.             // Header (negative positions will throw an ArrayIndexOutOfBoundsException)  
  285.             int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;  
  286.             if (position < numHeadersAndPlaceholders) {  
  287.                 if (position % mNumColumns == 0) {  
  288.                     return mHeaderViewInfos.get(position / mNumColumns).data;  
  289.                 }  
  290.                 return null;  
  291.             }  
  292.             // Adapter  
  293.             final int adjPosition = position - numHeadersAndPlaceholders;  
  294.             int adapterCount = 0;  
  295.             if (mAdapter != null) {  
  296.                 adapterCount = mAdapter.getCount();  
  297.                 if (adjPosition < adapterCount) {  
  298.                     return mAdapter.getItem(adjPosition);  
  299.                 }  
  300.             }  
  301.             throw new ArrayIndexOutOfBoundsException(position);  
  302.         }  
  303.         @Override  
  304.         public long getItemId(int position) {  
  305.             int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;  
  306.             if (mAdapter != null && position >= numHeadersAndPlaceholders) {  
  307.                 int adjPosition = position - numHeadersAndPlaceholders;  
  308.                 int adapterCount = mAdapter.getCount();  
  309.                 if (adjPosition < adapterCount) {  
  310.                     return mAdapter.getItemId(adjPosition);  
  311.                 }  
  312.             }  
  313.             return -1;  
  314.         }  
  315.         @Override  
  316.         public boolean hasStableIds() {  
  317.             if (mAdapter != null) {  
  318.                 return mAdapter.hasStableIds();  
  319.             }  
  320.             return false;  
  321.         }  
  322.         @Override  
  323.         public View getView(int position, View convertView, ViewGroup parent) {  
  324.             // Header (negative positions will throw an ArrayIndexOutOfBoundsException)  
  325.             int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns ;  
  326.             if (position < numHeadersAndPlaceholders) {  
  327.                 View headerViewContainer = mHeaderViewInfos  
  328.                         .get(position / mNumColumns).viewContainer;  
  329.                 if (position % mNumColumns == 0) {  
  330.                     return headerViewContainer;  
  331.                 } else {  
  332.                     if (convertView == null) {  
  333.                         convertView = new View(parent.getContext());  
  334.                     }  
  335.                     // We need to do this because GridView uses the height of the last item  
  336.                     // in a row to determine the height for the entire row.  
  337.                     convertView.setVisibility(View.INVISIBLE);  
  338.                     convertView.setMinimumHeight(headerViewContainer.getHeight());  
  339.                     return convertView;  
  340.                 }  
  341.             }  
  342.             // Adapter  
  343.             final int adjPosition = position - numHeadersAndPlaceholders;  
  344.             int adapterCount = 0;  
  345.             if (mAdapter != null) {  
  346.                 adapterCount = mAdapter.getCount();  
  347.                 if (adjPosition < adapterCount) {  
  348.                     return mAdapter.getView(adjPosition, convertView, parent);  
  349.                 }  
  350.             }  
  351.             throw new ArrayIndexOutOfBoundsException(position);  
  352.         }  
  353.         @Override  
  354.         public int getItemViewType(int position) {  
  355.             int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;  
  356.             if (position < numHeadersAndPlaceholders && (position % mNumColumns != 0)) {  
  357.                 // Placeholders get the last view type number  
  358.                 return mAdapter != null ? mAdapter.getViewTypeCount() : 1;  
  359.             }  
  360.             if (mAdapter != null && position >= numHeadersAndPlaceholders) {  
  361.                 int adjPosition = position - numHeadersAndPlaceholders;  
  362.                 int adapterCount = mAdapter.getCount();  
  363.                 if (adjPosition < adapterCount) {  
  364.                     return mAdapter.getItemViewType(adjPosition);  
  365.                 }  
  366.             }  
  367.             return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;  
  368.         }  
  369.         @Override  
  370.         public int getViewTypeCount() {  
  371.             if (mAdapter != null) {  
  372.                 return mAdapter.getViewTypeCount() + 1;  
  373.             }  
  374.             return 2;  
  375.         }  
  376.         @Override  
  377.         public void registerDataSetObserver(DataSetObserver observer) {  
  378.             mDataSetObservable.registerObserver(observer);  
  379.             if (mAdapter != null) {  
  380.                 mAdapter.registerDataSetObserver(observer);  
  381.             }  
  382.         }  
  383.         @Override  
  384.         public void unregisterDataSetObserver(DataSetObserver observer) {  
  385.             mDataSetObservable.unregisterObserver(observer);  
  386.             if (mAdapter != null) {  
  387.                 mAdapter.unregisterDataSetObserver(observer);  
  388.             }  
  389.         }  
  390.         @Override  
  391.         public Filter getFilter() {  
  392.             if (mIsFilterable) {  
  393.                 return ((Filterable) mAdapter).getFilter();  
  394.             }  
  395.             return null;  
  396.         }  
  397.         @Override  
  398.         public ListAdapter getWrappedAdapter() {  
  399.             return mAdapter;  
  400.         }  
  401.         public void notifyDataSetChanged() {  
  402.             mDataSetObservable.notifyChanged();  
  403.         }  
  404.     }  
  405. }  
代码地址:https://android.googlesource.com/platform/packages/apps/Gallery2/+/idea133/src/com/android/photos/views/HeaderGridView.java

阅读更多

没有更多推荐了,返回首页