1.https://github.com/maurycyw/StaggeredGridView 此鏈接有圖片加載功能,但功能相對簡單些。
2.https://github.com/etsy/AndroidStaggeredGrid 提供的瀑布流功能強大,可以自定義瀑布流列數。
本篇博客,就講解etsy的源碼爲主了。首先看效果圖:
首先明確StaggeredGridView中幾個變量的定義:
- private int mColumnCount; /*程序默認瀑布流的列數,默認情況,通過資源文件中的integers.xml 中grid_column_count定義*/
- private int mItemMargin; /*程序默認瀑布流的的margin,通過layout文件activity_sgv.xml中的app:item_margin="8dp"定義*/
- private int mColumnWidth; /*程序瀑布流的列寬變量*/
- private boolean mNeedSync;
- private int mColumnCountPortrait = DEFAULT_COLUMNS_PORTRAIT; /*程序瀑布流豎屏列數*/
- private int mColumnCountLandscape = DEFAULT_COLUMNS_LANDSCAPE;/*程序瀑布流橫屏列數*/
針對瀑布流,搞清楚如下幾個問題,也算是吃透其中的原理了。
一.瀑布流的列數定義好了後,如何計算每列的寬度?
進入StaggeredGridActivity界面,瀑布流的UI效果已經出來了,主要看com.etsy.android.grid.StaggeredGridView的onMeasure方法,確定每一個view的尺度.
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mColumnCount <= 0) {
- boolean isLandscape = isLandscape();
- mColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;
- }
- // our column width is the width of the listview
- // minus it's padding
- // minus the total items margin
- // divided by the number of columns
- mColumnWidth = calculateColumnWidth(getMeasuredWidth());
- private int calculateColumnWidth(final int gridWidth) {
- final int listPadding = getRowPaddingLeft() + getRowPaddingRight();
- return (gridWidth - listPadding - mItemMargin * (mColumnCount + 1)) / mColumnCount;
- }
資源文件list_item_sample.xml其定義如下:
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/panel_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
- <com.etsy.android.grid.util.DynamicHeightTextView
- android:id="@+id/txt_line1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="true"
- android:background="@drawable/list_item_selector"
- android:gravity="center" />
- <Button
- android:id="@+id/btn_go"
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:layout_gravity="top|right"
- android:text="Go" />
- </FrameLayout>
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mHeightRatio > 0.0) {
- // set the image views size
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = (int) (width * mHeightRatio);
- setMeasuredDimension(width, height);
- }
- else {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
- private double getRandomHeightRatio() {
- 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方法如下:
- @Override
- public void setAdapter(final ListAdapter adapter) {
- if (mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mObserver);
- }
- // use a wrapper list adapter if we have a header or footer
- if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
- mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
- }
- else {
- mAdapter = adapter;
- }
- public class HeaderViewListAdapter implements WrapperListAdapter, Filterable
- public int getCount() {
- if (mAdapter != null) {
- return getFootersCount() + getHeadersCount() + mAdapter.getCount();
- } else {
- return getFootersCount() + getHeadersCount();
- }
- }
- public int getItemViewType(int position) {
- int numHeaders = getHeadersCount();
- 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;
- }
getItemViewType函數即更加參數position,來確定view type id,view是從getView函數中創建的。
該函數的意思是:head和footer的位置,返回AdapterView.ITEM_VIEW_TYPE_HEADER_OF_FOOTER,普通的瀑布流item,將返回mAdapter.getItemViewType(adjPosition)。接着重寫getView函數。
- public View getView(int position, View convertView, ViewGroup parent) {
- // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
- int numHeaders = getHeadersCount();
- if (position < numHeaders) {
- return mHeaderViewInfos.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);
- }
- }
- // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
- return mFooterViewInfos.get(adjPosition - adapterCount).view;
- }
至此,就明白了Header和footer是如何被添加到瀑布流界面了。接下來,就來確定head和footer的高度寬度問題了。瀑布流中,從UI效果來看,有三種類型的type view,一個是head,一個是StaggeredGridView,另外一個是footer了。計算子view的尺寸,當然要關注onMeasureChild函數了。StaggeredGridView.java中的onMeasureChild函數定義如下:
- @Override
- protected void onMeasureChild(final View child, final LayoutParams layoutParams) {
- final int viewType = layoutParams.viewType;
- final int position = layoutParams.position;
- if (viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
- viewType == ITEM_VIEW_TYPE_IGNORE) {
- // for headers and weird ignored views
- super.onMeasureChild(child, layoutParams);
- }
可以看出,當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中看出端倪:
- <com.etsy.android.grid.util.DynamicHeightTextView
- android:id="@+id/txt_line1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clickable="true"
- android:background="@drawable/list_item_selector"
- android:gravity="center" />
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- shapes defined for Android 2.3 drawable issues -->
- <item android:state_pressed="true" >
- <shape android:shape="rectangle">
- <solid android:color="@color/list_item_pressed" />
- </shape>
- </item>
- <!-- default -->
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent" />
- </shape>
- </item>
- </selector>
這個selector的意思是,默認情況下,使用全透明效果
- <solid android:color="@android:color/transparent" />
使用這種機制的原因,也很容易理解,在SampleAdapter中的getView函數中,代碼:
- 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,需要的讀者,請自行下載。