关于Android瀑布流控件,已经在江湖上,流传已久,超过两年时间了。网上也有很多相关学习资源,可以拿来研究学习。github上,就有两个资源,可以供学习者膜拜。

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;/*程序瀑布流横屏列数*/
针对瀑布流,搞清楚如下几个问题,也算是吃透其中的原理了。

1.瀑布流的列数定义好了后,如何计算每列的宽度? 2.瀑布流的列数定义好了后,如何计算每列的高度? 3.瀑布流的HeaderView是如何添加的,其高度宽度如何确定? 4.瀑布流的FooterView是如何添加的,其高度宽度如何确定? 5.点击瀑布流的item时,高亮和默认背景的selector如何来实现?

一.瀑布流的列数定义好了后,如何计算每列的宽度?

     进入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());       ... .... ... .... ... ....
变量 mColumnCount是从资源文件 integers.xml 中grid_column_count获取,默认是2,函数 calculateColumnWidth用于计算列宽,其定义也比较简单,容易理解.
    private int calculateColumnWidth(final int gridWidth) {        final int listPadding = getRowPaddingLeft() + getRowPaddingRight();        return (gridWidth - listPadding - mItemMargin * (mColumnCount + 1)) / mColumnCount;    }
即屏幕宽度 -  listPadding  -  mItemMargin  除以 列数即item的宽度.此时此刻,列宽即可以得到。继续往下debug代码,由于是使用SampleAdapter,瀑布流中的每一个item均要调用getView方法,此方法跟所有的Adapter一样,要从layout文件中,导入用户自定义的布局文件。代码为:convertView = mLayoutInflater.inflate(R.layout.list_item_sample, parent, false);

资源文件list_item_sample.xml其定义如下:

<?xml version="1.0" encoding="utf-8"?>        
       此处需要了解DynamicHeightTextView 的定义了。整个Adapter中,子item的尺度,均由DynamicHeightTextView 来确定。其类中的onMeasure如下:

    @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);        }    }
 遍历 mHeightRatio宽高比,从setHeightRatio中获取,经过层层代码跟踪,瀑布流中子item的高度已经剖析出来了。子view的宽高确定,首先要根据屏幕尺寸,确定宽度 然后根据SampleAdapter中的 getRandomHeightRatio函数,确定高度,高度是宽度的1~1.5倍。
    private double getRandomHeightRatio() {        return (mRandom.nextDouble() / 2.0) + 1.0; // height will be 1.0 - 1.5 the width    }
     剖析至此,可以回答上述的问题1和2了。
二.瀑布流的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;        }     ... ...  ... ... ... ...
mHeaderViewInfos或者 mFooterViewInfos的size不为0时,构造一个 HeaderViewListAdapter。其定义如下

public class HeaderViewListAdapter implements WrapperListAdapter, Filterable
从定义看以看出,该对象属于一个 ListAdapter,既然是Adapter,当然逃不脱几个重要的函数了。getCount(),getItemViewType(),getView()。   
    public int getCount() {        if (mAdapter != null) {            return getFootersCount() + getHeadersCount() + mAdapter.getCount();        } else {            return getFootersCount() + getHeadersCount();        }    }
    getCount即adapter的item数量,当mAdapter!=null时,返回head和footer加上 mAdapter.getCount.这样。head和footer也就与普通的瀑布流item一起,作为adapter的元素了。   
    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_OR_FOOTER,普通的瀑布流item,将返回mAdapter.getItemViewType(adjPosition)。

     接着就分析getView函数了。任何一个Adapter,都要重写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位置,将返回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函数定义如下:

    @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);        }        else { ... ...  ... ...  ... ...
   可以看出,当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中看出端倪:

    
代码:android:background="@drawable/list_item_selector"中, list_item_selector的定义如下:

<?xml version="1.0" encoding="utf-8"?>                                                                                  

   这个selector的意思是,默认情况下,使用全透明效果

点中状态时,呈现的效果

   使用这种机制的原因,也很容易理解,在SampleAdapter中的getView函数中,代码:

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

表示不同postion的item,显示不同的颜色,那么selector的定义,默认的情况时,使用全透明效果,UI上,就可以看到item的背景色,不会被selector的默认颜色遮挡。

    由于在https://github.com/etsy/AndroidStaggeredGrid中的项目工程,是在Android Studio中构建的,本人也使用过AS一段时间,发现有很多坑,不习惯,故将代码pull下来后,重新创建工程,在Eclipse中build通过,项目地址在:https://github.com/hero-peng/My-StaggeredGridView,需要的读者,请自行下载。

 

更多相关文章

  1. Android使用DataBinding实现MVVM模式(基本使用)
  2. Android(安卓)Studio 自定义皮肤主题和背景
  3. 如何在Android设备上识别读取美国驾照信息
  4. Android之旅十六 android中各种资源的使用
  5. Android(安卓)中TabLayout自定义选择背景滑块的实例代码
  6. android studio常用快捷键整理
  7. [android] 调试linux input子系统驱动的用户空间命令 getevent/s
  8. Android(安卓)自定义View探索——图片
  9. Android自定义控件(一)

随机推荐

  1. 欢迎进入Android的世界
  2. Android之我当年爬过的坑
  3. Android修改APP版本号
  4. Android(安卓)UI 之 我的页面 圆形图片+
  5. android最佳实践(五)
  6. Android单行显示ellipse和singleLine
  7. android基础入门(二)——创建android工程
  8. 如何安装apk文件在Android仿真器中
  9. Android探索之旅 | Android简介
  10. Android音频开发(6):Mp3的录制 - 使用Lame实