Android瀑布流StaggeredGridView学习研究

关于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"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:id="@+id/panel_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants"
    android:background="@drawable/recommend_app_bg">

    <com.etsy.android.grid.util.DynamicHeightTextView
        android:id="@+id/txt_line1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"/>

    <Button
        android:id="@+id/btn_go"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|right"
        android:text="Go" />

</FrameLayout>

此处需要了解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如何来实现

这个在下一个blog中进行研究。

时间: 2024-08-11 13:19:19

Android瀑布流StaggeredGridView学习研究的相关文章

转载—— android 瀑布流的实现详解,附源码

介绍 参考自:https://github.com/dodola/android_waterfall,因为原来的代码封装不好,所以,我根据源码的思路,重新写了一遍,所以有了现在这个项目:https://github.com/youxilua/waterfall4android 原作者表示: 试过在1万张可以流畅的滑动,不出现内存溢出情况 设计思路 之前的作者的自定义view 只有主滑动一层,其他的设置要在相应的活动设置,个人觉得,重用起来比较麻烦,所以决定封装一层.现在定义一个默认的瀑布流只需5

Android瀑布流照片墙实现,体验不规则排列的美感Demo

Android瀑布流照片墙实现,体验不规则排列的美感Demo.略有逼格~ 下载地址:http://www.devstore.cn/code/info/637.html

Android 瀑布流demo

原文:Android 瀑布流demo 源代码下载地址:http://www.zuidaima.com/share/1550463780342784.htm Android 中实现瀑布流列表

Android瀑布流,解决oom

这是一个Android瀑布流的实现demo. 瀑布流我的实现是定义三个linearlayout,然后向里面addView(),如果多了会出现oom异常,所以做了一些处理. 1.lrucache缓存 2.只显示当前屏的图片 3.滑动过程中不加载图片 4.大图缩放成小图 直接看代码: PhotoFallScrollView.java主类 自定义的ScrollView. package com.pangzaifei.falls; import java.io.BufferedInputStream;

android瀑布流效果(仿蘑菇街)

Android 转载分享(10)  我们还是来看一款示例:(蘑菇街)           看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此.就如我们的方角图形,斯通见惯后也就出现了圆角.下面我简单介绍下实现方法. 第一种: 我们在配置文件中定义好列数.如上图也就是3列.我们需要定义三个LinearLayout,然后把获取到的图片add里面就ok了. main.xml [java] view plaincopy <?xml version

Android瀑布流照片墙实现,体验不规则排列的美感

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/10470797 传统界面的布局方式总是行列分明.坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳.这个时候瀑布流布局的出现,就给人带来了耳目一新的感觉,这种布局虽然看上去貌似毫无规律,但是却有一种说不上来的美感,以至于涌现出了大批的网站和应用纷纷使用这种新颖的布局来设计界面. 记得我在之前已经写过一篇关于如何在Android上实现照片墙功能的文章了,但那个

Android瀑布流照片

http://blog.csdn.net/guolin_blog/article/details/10470797 记得我在之前已经写过一篇关于如何在Android上实现照片墙功能的文章了,但那个时候是使用的GridView来进行布局的,这种布局方式只适用于“墙”上的每张图片大小都相同的情况,如果图片的大小参差不齐,在GridView中显示就会非常的难看.而使用瀑布流的布局方式就可以很好地解决这个问题,因此今天我们也来赶一下潮流,看看如何在Android上实现瀑布流照片墙的功能. 首先还是讲一下

android 瀑布流

我们还是来看一款示例: 看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此.就如我们的方角图形,斯通见惯后也就出现了圆角.下面我简单介绍下实现方法. 第一种: 我们在配置文件中定义好列数.如上图也就是3列.我们需要定义三个LinearLayout,然后把获取到的图片add里面就ok了. main.xml [java] view plaincopy <?xml version="1.0" encoding="utf

android 瀑布流效果(仿蘑菇街)

我们还是来看一款示例:(蘑菇街)           看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此.就如我们的方角图形,斯通见惯后也就出现了圆角.下面我简单介绍下实现方法. 第一种: 我们在配置文件中定义好列数.如上图也就是3列.我们需要定义三个LinearLayout,然后把获取到的图片add里面就ok了. main.xml [java] view plaincopy <?xml version="1.0" enc