瀑布流StaggeredGridView

  關於Android瀑布流控件相關學習資源,github上有兩個資源,可以供學習者膜拜。

1.https://github.com/maurycyw/StaggeredGridView    此鏈接有圖片加載功能,但功能相對簡單些。

2.https://github.com/etsy/AndroidStaggeredGrid  提供的瀑布流功能強大,可以自定義瀑布流列數。

      本篇博客,就講解etsy的源碼爲主了。首先看效果圖:

                                                                       

首先明確StaggeredGridView中幾個變量的定義:

  1. private int mColumnCount;  /*程序默認瀑布流的列數,默認情況,通過資源文件中的integers.xml 中grid_column_count定義*/  
  2. private int mItemMargin;   /*程序默認瀑布流的的margin,通過layout文件activity_sgv.xml中的app:item_margin="8dp"定義*/  
  3. private int mColumnWidth;   /*程序瀑布流的列寬變量*/  
  4. private boolean mNeedSync;  
  5. private int mColumnCountPortrait = DEFAULT_COLUMNS_PORTRAIT; /*程序瀑布流豎屏列數*/  
  6. private int mColumnCountLandscape = DEFAULT_COLUMNS_LANDSCAPE;/*程序瀑布流橫屏列數*/  

針對瀑布流,搞清楚如下幾個問題,也算是吃透其中的原理了。


1.瀑布流的列數定義好了後,如何計算每列的寬度?
2.瀑布流的列數定義好了後,如何計算每列的高度?
3.瀑布流的HeaderView是如何添加的,其高度寬度如何確定?
4.瀑布流的FooterView是如何添加的,其高度寬度如何確定?
5.點擊瀑布流的item時,高亮和默認背景的selector如何來實現?

一.瀑布流的列數定義好了後,如何計算每列的寬度?

     進入StaggeredGridActivity界面,瀑布流的UI效果已經出來了,主要看com.etsy.android.grid.StaggeredGridView的onMeasure方法,確定每一個view的尺度.

  1. @Override  
  2. protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {  
  3.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  4.     if (mColumnCount <= 0) {   
  5.         boolean isLandscape = isLandscape();  
  6.         mColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;  
  7.     }  
  8.     // our column width is the width of the listview   
  9.     // minus it's padding  
  10.     // minus the total items margin  
  11.     // divided by the number of columns  
  12.     mColumnWidth = calculateColumnWidth(getMeasuredWidth());  
變量mColumnCount是從資源文件integers.xml 中grid_column_count獲取,默認是2,函數calculateColumnWidth用於計算列寬,其定義也比較簡單,容易理解.
  1. private int calculateColumnWidth(final int gridWidth) {  
  2.     final int listPadding = getRowPaddingLeft() + getRowPaddingRight();  
  3.     return (gridWidth - listPadding - mItemMargin * (mColumnCount + 1)) / mColumnCount;  
  4. }  
即屏幕寬度 - listPadding - mItemMargin 除以 列數即item的寬度.此時此刻,列寬即可以得到。繼續往下debug代碼,由於是使用SampleAdapter,瀑布流中的每一個item均要調用getView方法,此方法跟所有的Adapter一樣,要從layout文件中,導入用戶自定義的佈局文件。代碼爲:convertView = mLayoutInflater.inflate(R.layout.list_item_sample, parent, false);

資源文件list_item_sample.xml其定義如下:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/panel_content"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="horizontal" >  
  7.     <com.etsy.android.grid.util.DynamicHeightTextView  
  8.         android:id="@+id/txt_line1"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:clickable="true"  
  12.         android:background="@drawable/list_item_selector"  
  13.         android:gravity="center" />  
  14.     <Button  
  15.         android:id="@+id/btn_go"  
  16.         android:layout_width="60dp"  
  17.         android:layout_height="60dp"  
  18.         android:layout_gravity="top|right"  
  19.         android:text="Go" />  
  20. </FrameLayout>  
       此處需要了解DynamicHeightTextView 的定義了。整個Adapter中,子item的尺度,均由DynamicHeightTextView 來確定。其類中的onMeasure如下:
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     if (mHeightRatio > 0.0) {  
  4.         // set the image views size  
  5.         int width = MeasureSpec.getSize(widthMeasureSpec);  
  6.         int height = (int) (width * mHeightRatio);  
  7.         setMeasuredDimension(width, height);  
  8.     }  
  9.     else {  
  10.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  11.     }  
  12. }  
 遍歷mHeightRatio寬高比,從setHeightRatio中獲取,經過層層代碼跟蹤,瀑布流中子item的高度已經剖析出來了。子view的寬高確定,首先要根據屏幕尺寸,確定寬度
然後根據SampleAdapter中的getRandomHeightRatio函數,確定高度,高度是寬度的1~1.5倍。

  1. private double getRandomHeightRatio() {  
  2.     return (mRandom.nextDouble() / 2.0) + 1.0; // height will be 1.0 - 1.5 the width  

二.瀑布流的HeaderView和FooterView是如何添加的,其高度寬度如何確定?

    首先看效果圖:

                                                                           

       從StaggeredGridActivity來看,就只有簡單的一行代碼,mGridView.addHeaderView(header),便可以給list增加head了。瀑布流的SampleAdapter如何跟headview結合在一起呢?

     在mGridView.setAdapter(mAdapter)時,調用ExtendableListView的setAdapter方法,ExtendableListView繼承於AbsListView。ExtendableListView.java 的setAdapter方法如下:  

  1. @Override  
  2. public void setAdapter(final ListAdapter adapter) {  
  3.     if (mAdapter != null) {  
  4.         mAdapter.unregisterDataSetObserver(mObserver);  
  5.     }  
  6.     // use a wrapper list adapter if we have a header or footer  
  7.     if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {  
  8.         mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);  
  9.     }  
  10.     else {  
  11.         mAdapter = adapter;  
  12.     }  
mHeaderViewInfos或者mFooterViewInfos的size不爲0時,構造一個HeaderViewListAdapter。其定義如下

  1. public class HeaderViewListAdapter implements WrapperListAdapter, Filterable  
從定義看以看出,該對象屬於一個ListAdapter,既然是Adapter,當然逃不脫幾個重要的函數了。getCount(),getItemViewType(),getView()。  
  1. public int getCount() {  
  2.     if (mAdapter != null) {  
  3.         return getFootersCount() + getHeadersCount() + mAdapter.getCount();  
  4.     } else {  
  5.         return getFootersCount() + getHeadersCount();  
  6.     }  
  7. }  
   getCount即adapter的item數量,當mAdapter!=null時,返回head和footer加上mAdapter.getCount.這樣。head和footer也就與普通的瀑布流item一起,作爲adapter的元素了。  
  1. public int getItemViewType(int position) {  
  2.     int numHeaders = getHeadersCount();  
  3.     if (mAdapter != null && position >= numHeaders) {  
  4.         int adjPosition = position - numHeaders;  
  5.         int adapterCount = mAdapter.getCount();  
  6.         if (adjPosition < adapterCount) {  
  7.             return mAdapter.getItemViewType(adjPosition);  
  8.         }  
  9.     }  
  10.     return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;  
  11. }  

     getItemViewType函數即更加參數position,來確定view type id,view是從getView函數中創建的。

該函數的意思是:head和footer的位置,返回AdapterView.ITEM_VIEW_TYPE_HEADER_OF_FOOTER,普通的瀑布流item,將返回mAdapter.getItemViewType(adjPosition)。接着重寫getView函數。

  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.     // Header (negative positions will throw an ArrayIndexOutOfBoundsException)  
  3.     int numHeaders = getHeadersCount();  
  4.     if (position < numHeaders) {  
  5.         return mHeaderViewInfos.get(position).view;  
  6.     }  
  7.     // Adapter  
  8.     final int adjPosition = position - numHeaders;  
  9.     int adapterCount = 0;  
  10.     if (mAdapter != null) {  
  11.         adapterCount = mAdapter.getCount();  
  12.         if (adjPosition < adapterCount) {  
  13.             return mAdapter.getView(adjPosition, convertView, parent);  
  14.         }  
  15.     }  
  16.     // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)  
  17.     return mFooterViewInfos.get(adjPosition - adapterCount).view;  
  18.  
     函數的意思理解也很容易,header位置,將返回mHeaderViewInfos.get(position).view;footer位置,返回mFooterViewInfos.get(adjPosition - adapterCount).view。其他的位置,也就是不規則GridView中的item view,返回mAdapter.getView(adjPosition, convertView, parent),mAdapter對象,將會調用到SampleAdapter.java中的getView方法了。

     至此,就明白了Header和footer是如何被添加到瀑布流界面了。接下來,就來確定head和footer的高度寬度問題了。瀑布流中,從UI效果來看,有三種類型的type view,一個是head,一個是StaggeredGridView,另外一個是footer了。計算子view的尺寸,當然要關注onMeasureChild函數了。StaggeredGridView.java中的onMeasureChild函數定義如下:

  1.    @Override  
  2.    protected void onMeasureChild(final View child, final LayoutParams layoutParams) {  
  3.        final int viewType = layoutParams.viewType;  
  4.        final int position = layoutParams.position;  
  5.   
  6.        if (viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||  
  7.                viewType == ITEM_VIEW_TYPE_IGNORE) {  
  8.            // for headers and weird ignored views  
  9.            super.onMeasureChild(child, layoutParams);  
  10.        }

   可以看出,當viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER時,調用父類ExtendableListView的onMeasureChild方法,計算head的尺度;當viewType!=ITEM_VIEW_TYPE_HEADER_OR_FOOTER時,走else流程,根據int childWidthSpec = MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY) 來計算尺 寸。

三.點擊瀑布流的item時,高亮和默認背景的selector如何來實現


   先上效果圖:

    從github上下載的源代碼,經過稍微的修改,便可以實現點擊item高亮效果,還是在資源佈局文件list_item_sample.xml中看出端倪:

  1. <com.etsy.android.grid.util.DynamicHeightTextView  
  2.     android:id="@+id/txt_line1"  
  3.     android:layout_width="wrap_content"  
  4.     android:layout_height="wrap_content"  
  5.     android:clickable="true"  
  6.     android:background="@drawable/list_item_selector"  
  7.     android:gravity="center" />  
代碼:android:background="@drawable/list_item_selector"中,list_item_selector的定義如下:
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <!-- shapes defined for Android 2.3 drawable issues -->  
  4.     <item android:state_pressed="true" >  
  5.         <shape android:shape="rectangle">  
  6.              <solid android:color="@color/list_item_pressed" />  
  7.         </shape>  
  8.     </item>  
  9.     <!-- default -->  
  10.     <item>  
  11.         <shape android:shape="rectangle">  
  12.              <solid android:color="@android:color/transparent" />  
  13.         </shape>  
  14.     </item>  
  15. </selector>  

   這個selector的意思是,默認情況下,使用全透明效果

  1. <solid android:color="@android:color/transparent" />  
點中狀態時,呈現<solid android:color="@color/list_item_pressed" />的效果

   使用這種機制的原因,也很容易理解,在SampleAdapter中的getView函數中,代碼:

  1. convertView.setBackgroundResource(mBackgroundColors.get(backgroundIndex));  

表示不同postion的item,顯示不同的顏色,那麼selector的定義,默認的情況時,使用全透明效果,UI上,就可以看到item的背景色,不會被selector的默認顏色遮擋。

    由於在https://github.com/etsy/AndroidStaggeredGrid中的項目工程,是在Android Studio中構建的,如果不習慣,可以使用eclipse中的,項目地址在:https://github.com/hero-peng/My-StaggeredGridView,需要的讀者,請自行下載。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章