使用RecyclerView实现瀑布流

瀑布流也是个常用的显示控件了,但是在使用时经常遇到一些问题,比如滑动回顶部后出现空隙、item在滑动时乱跳等问题。

下面就来说说我怎么实现的瀑布流,并且怎么处理上面所说的这些问题的。

我使用了原生控件RecyclerView+StaggeredGridLayoutManager来实现的瀑布流,没有用第三方开源框架。下面以2列的瀑布流为例子开始讲解。

因为使用了StaggeredGridLayoutManager实现瀑布流,但是在设置后发现图片在滑动加载过程中高度会发生变化,在网上搜索了很多资料后,总结解决办法是在onBindViewHolder中绑定View时,给ImageView设置宽高,就能解决这个问题。

先看一下最终实现效果:

正常显示的瀑布流.gif

提前说明下,我使用的是Glide3,读者们可以自行修改为Glide4。

1.实现瀑布流

先说说实现思路:

  • 写布局文件,分别有2个布局文件,Activity的布局文件和Adapter的布局文件
  • 写适配器,瀑布流的适配器里需要设置ImageView的宽高。
  • 写RecyclerView,给RecyclerView设置StaggeredGridLayoutManager并设置适配器。
  • 添加数据测试效果,根据效果反馈进行修改

第一步:写布局文件

Activity的布局文件只有一个RecyclerView就不贴了,贴一下Adapter的布局文件:

adapter_item_card.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/card"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:background="@android:color/holo_green_dark"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/card_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/card_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="2dp"
        tools:text="hello" />
</LinearLayout>

第二步:写适配器

适配器中包含数据Card的集合,Card类包含如下几个属性:

    private String title;
    private String img_url;
    private int width;
    private int height;

在适配器中主要就是将数据绑定到view上,最关键的步骤是根据图片的宽高算出图片的宽高比,然后根据宽高比选择正方形显示,还是长方形显示,最后通过setLayoutParams方法来设置图片的宽高。

思路如下:

  • 计算图片宽度
  • 根据图片宽高比,确定图片使用正方形或是4比3的长方形显示
  • 使用setLayoutParams方法设置图片宽高
  • 使用Glide加载图片并用override重写图片宽高

适配器核心代码如下:

private final double STANDARD_SCALE = 1.1; //当图片宽高比例大于STANDARD_SCALE时,采用3:4比例,小于时,则采用1:1比例
private final float SCALE = 4 * 1.0f / 3;       //图片缩放比例
private List<Card> cards = new ArrayList<>();

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    Card card = mCards.get(position);
    setCardView(holder, card);
}

private void setCardView(ViewHolder holder, Card card) {
    //计算图片宽高
    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) holder.image.getLayoutParams();
    //2列的瀑布流,屏幕宽度减去两列间的间距space所的值再除以2,计算出单列的imageview的宽度,space的值在RecyclerView初始化时传入
    float itemWidth = (ScreenUtil.getScreenWidth(context) - space) / 2;
    layoutParams.width = (int) itemWidth;

    float width = card.getWidth();
    float height = card.getHeight();
    float scale = height / width;
    if (scale > STANDARD_SCALE) {
        //采用3:4显示
        layoutParams.height = (int) (itemWidth * SCALE);
    } else {
        //采用1:1显示
        layoutParams.height = (int) itemWidth;
    }
    holder.image.setLayoutParams(layoutParams);
    Glide.with(context).load(card.getImg_url()).asBitmap().placeholder(R.mipmap.ic_launcher)
            .diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().into(holder.image);
    holder.title.setText(card.getTitle());
}

写好适配器后,就可以在MAinActivity中初始化RecyclerView和适配器了,代码如下:

        int space = 20;
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//        layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);   //设置后瀑布流不显示了
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setItemAnimator(null);
        mRecyclerView.addItemDecoration(new StaggeredItemDecoration(space));//单位px
        mAdapter = new StaggeredGridAdapter(space);
        mAdapter.setCards(mCards);
        mRecyclerView.setAdapter(mAdapter);

在网上看到使用StaggeredGridLayoutManager的setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE)设置来处理瀑布流滑动到顶部空白的问题,结果发现添加这句代码后,整个瀑布流都不显示了,所以不能这样处理。

在上面的代码中我设置了space值,space是指两列卡片之间的距离,根据需求设置,这里space用在了2个地方分别是:

mRecyclerView.addItemDecoration(new StaggeredItemDecoration(space));
mAdapter = new StaggeredGridAdapter(space);

前者用于设置两列瀑布流之间的距离,后者是用来计算单列图片的宽度。StaggeredItemDecoration类的代码在此

代码写好后,来看看瀑布流效果。

瀑布流图片大小变化.gif

好像有点奇怪的地方,在滑动过程中前面图片1、2、3的大小发生了变化。我当时也是很疑惑,在网上搜索图片大小改变的问题原因也没有找到,好像与RecyclerView的图片缓存机制有关,有知道的胖友可以告知一下。

最后通过在Glide加载图片时添加override设置图片宽高解决了,关于override设置图片可以看看这篇文章《Glide的override方法和View的setLayoutParams方法设置图片宽高对比》

Glide.with(context).load(card.getImg_url()).asBitmap().placeholder(R.mipmap.ic_launcher)
                .diskCacheStrategy(DiskCacheStrategy.RESULT).override(layoutParams.width, layoutParams.height).centerCrop().into(holder.image);

解决后的效果如下,可以看到在滑动过程中,图片大小没有再变化:

正常显示的瀑布流.gif

2. 瀑布流顶部出现空隙、item乱跳等问题

照上面的处理已经能解决顶部出现空隙、item乱跳的问题,但是建议在瀑布流更新时采用notifyItemRangeInserted()方法更新,可以避免一些不必要的问题。

if (FIRST_PAGE_LAST_ID.equals(lastId)) {
    mAdapter.notifyDataSetChanged();//第一页更新
} else {
    mAdapter.notifyItemRangeInserted(startPosition, count);//第一页以外使用notifyItemRangeInserted()更新
}

如果对你有帮助的话,点赞、评论、赞赏都是对我的鼓励,也是支持我写下去的动力,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270