美文网首页优秀案例
使用RecyclerView实现瀑布流

使用RecyclerView实现瀑布流

作者: 程序员张晴天 | 来源:发表于2019-04-10 01:22 被阅读0次

    瀑布流也是个常用的显示控件了,但是在使用时经常遇到一些问题,比如滑动回顶部后出现空隙、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()更新
    }
    

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

    相关文章

      网友评论

        本文标题:使用RecyclerView实现瀑布流

        本文链接:https://www.haomeiwen.com/subject/wyfpbqtx.html