美文网首页android技术
RecyclerView实现瀑布流的各种坑

RecyclerView实现瀑布流的各种坑

作者: 雾中的影子 | 来源:发表于2017-08-30 17:15 被阅读2843次

    RecyclerView 实现瀑布流,关键是用StaggeredGridLayoutManager这个类。原以为很简单,用了之后才发现有很多的问题。

    • item乱跳
    • 滑动时有空白出现
    • 如果item高度不固定得时候,item内容不变的时候,可能出现同一个item高度可能会出现不同的值

    1. item乱跳问题

    StaggeredGridLayoutManager设置空隙处理方式为 不处理。

    setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE)
    

    2.滑动空白的问题

    设置了StaggeredGridLayoutManager不处理空白之后,发现反复滑动列表时,顶部item上边会出现空白。网络很多都是讲 监听onScrollListener,然后调用

    invalidateSpanAssignments();
    

    这个方法会重绘视图,在scroll中调用会显得非常频繁,然后引起界面卡顿,滑动不流畅等问题。

    本人优化了一下,在OnScrollStateChange方法中,但列表处于SCROLL_STATE_IDLE的时候才去调用这个方法,感觉卡顿方面好很多,但是偶尔还是会出现顶部空白的现象。所以这个不能从根本上解决问题,充其量算是一种弥补之法。

    其实产生这个问题的根本原因在于Item的高度,尤其是高度设置为 wrap_content这种不固定的状态。

    有很多人包括网上都说用map保存item的高度,尤其是当图片瀑布流不知道图片大小的时候,第一次保存起来,后面就直接从map里取值然后设置对应控件的高度。本人尝试之后,发现表面上看起来好像能解决问题,但是StaggeredGridLayoutManager布局跟其他的布局有点不一样的地方就是 横向的 item对应的position不确定,并不是像GridLayout那种从上到下,从左至右,position依次递增。假如列表为2列,那么有可能第二行的左边的position是2,右边是3。当你反复滑动几次之后,其实就是notiftyDataChanged几次之后,有可能会发现第二行的左边是3,右边是2。所以保存高度这种方式也不是很靠谱。

    折腾了两天之后,万般无赖之下,发现只有从接口传回的图片数据带上原始宽高,才能完美解决问题。

    在已经图片高度的情况下,一切都好办了,根据屏幕宽度计算出固定的item宽度,然后对原始图片进行等比缩放高度,然后在onBindViewHolder中设置动态设置ImageView 的高度就好了,这时候也不用map保存什么,也不需要调用invalidateSpanAssignments方法去重绘,因为已经不会出现空白了。

    3.RecyclerView设置item间隔问题

    刚才已经提到,StaggeredGridLayoutManager不能根据item 的 position来判断一个item是靠在左边还是右边。所以之间定义的SpaceItemDecoration不能用了,现在的解决办法是 定义一个简单的SpaceItemDecoration,代码如下:

    public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
    
        private int spanCount;
        private int space;
        private boolean includeEdge;
    
    
        public SpaceItemDecoration(int spanCount, int space) {
            this.spanCount = spanCount;
            this.space = space;
    
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view); // item position
            outRect.left = space;
            outRect.right = space;
            if(position!=0 && position!=1){
                outRect.top = 2*space;
            }else{
                outRect.top = space;
            }
    
    
        }
    }
    

    这样会发现两列中间的间隔是 边缘的两倍。我的解决办法是 给RecyclerView设置一定的padding,让视图看起来,四周,中间 的间隔看起来都一样大。相当于SpaceItemDecoration,不够,还需RecyclerView补一刀。

    当然网上也有人用变量把Item是左边还是右边这种数据存起来,我觉的有点麻烦,而且第一次布局怎么办。或许还有更好更完美的办法等着我们去发现。

    4.上拉加载问题

    因为StaggeredGridLayoutManager 布局item 的position特殊性,就连findLastVisibleItemPositions方法的参数和返回值都不一样,这个是返回一个position 数组。废话不多说,本人自定义了一个StaggerRecyclerView专门针对StaggeredGridLayoutManager布局。代码如下:

    public class StaggerRecyclerView extends RecyclerView {
    
        private OnLoadMoreListener onLoadMoreListener;
        private boolean isLoadingMore = false;
        private static final int TOLAST = 6;
    
        public StaggerRecyclerView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            this.addOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    StaggeredGridLayoutManager layoutManager = null ;
                    if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager){
                        layoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();
                    }else{
                        return;
                    }
                    int[] positions = null;
                    int[] into = layoutManager.findLastCompletelyVisibleItemPositions(positions);
                    int lastPositon = Math.max(into[0],into[1]);
                    for(int i = 0;i<into.length;i++){
                        Log.d("home","lastPositon ="+lastPositon +" | itemcount ="+layoutManager.getItemCount()+" | dx = "+dx+" | dy = "+dy);
                    }
    
                    if(!isLoadingMore && dy>0 && layoutManager.getItemCount()-lastPositon<=TOLAST){
                        //load more
                        isLoadingMore = true;
                        if(onLoadMoreListener!=null){
                            onLoadMoreListener.onLoadMore();
                        }
    
                    }
                }
            });
        }
    
        public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
            this.onLoadMoreListener = onLoadMoreListener;
        }
    
        public void setLoadingMoreComplete(){
            isLoadingMore = false;
        }
    
        public  interface OnLoadMoreListener{
            void onLoadMore();
        }
    
        
    }
    

    其中 TOLAST是我定义的一个常亮,主要是决定什么时候开始加载,数字越大越提前加载,它表示提前几个item去加载。相对于最后一个而言。往往当我们上拉的时候,如果等到最后一个item可见的时候才去加载,可能会因为加载需要时间,造成短暂的停留,体验不好。瀑布流嘛,最好让用户感知不到你的加载动作,让他能一直顺畅的滑下去。

    总结一点:实现瀑布流最关键的就是高度问题,完美的解决方案就是传回图片的时候顺便把高度也传回来。

    相关文章

      网友评论

      • 稀有皮皮虾丶:老哥 加载更多的时候 回滑到顶部 item总是会换位置是怎么回事
      • Kuma_233:分析的很到位。初学者遇到问题很多。谢谢你
      • 我在寻找一个梦想:GlideApp.with(context)
        .load(photo.getImgUrl())
        .override(lp.width,lp.height)
        .apply(options)
        .transition(DrawableTransitionOptions.withCrossFade())
        .listener(loadListener)
        .into(holder.ivPhoto);

        这段代码中option是啥
        雾中的影子:@预言家小兵 https://gitee.com/liangshan/Future.git
        预言家小兵:我写的方法如下1,
        int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;//屏幕的宽度(px值)
        //Item的宽度,或图片的宽度
        int width = screenWidth/2;
        int h= (int) ((width*item.getPhotosHeight())/(item.getPhotosWidth()));
        ImageView imageView= helper.getView(R.id.ivPic);
        ViewGroup.LayoutParams params = imageView.getLayoutParams();//得到item的LayoutParams布局参数
        params.height = h;//把随机的高度赋予item布局
        params.width=width;
        LogUtil.e(getClass().getName(),"width=="+width+"\n h==="+h+"\n" +
        " geth=="+item.getPhotosHeight()+"\n getw==="+item.getPhotosWidth());
        imageView.setLayoutParams(params);
        Glide.with(mContext).load(item.getCover())
        .centerCrop()
        .error(R.drawable.ic_load_default_img)
        .placeholder(R.drawable.ic_load_default_img)
        .fitCenter()
        .override(params.width,params.height)
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .into(imageView);

        2. 给RecyclerView做处理,
        final StaggeredGridLayoutManager staggeredGridLayoutManager=
        new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
        adapter = new HomeMainAdapter(R.layout.item_home_product_goods_demo);
        mRecyclerView.setAdapter(adapter);
        mRecyclerView.setFocusable(false);
        staggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
        效果还是滑动到最下面。再滑动到最上面的时候,还是会留白,楼主可以给出demo源码吗?
        雾中的影子:@我在寻找一个梦想 Contains {@link Drawable} specific animation options.glide 里面的类,包含了指定动画的类
      • 阳光底下的那个少年:我和你遇到了一模一样的问题,一直没想到解决的方案,谢谢你的分享
      • 1c2f86328c2c:间隙问题,如果RecycleView是有带头部,要求头部必须蛮宽,可是item要均等间隙,这个要怎么搞
      • hasonguo:我这面瀑布流上下滚动多次,会出现下面的图片有空白的现象,有时候左边的图片明显多于右边,有时候右边的图片要多于左边,不知道为什么
        hasonguo:@hasonguo 在adapter的onbindviewholder有设置,这个高度都是服务器给的
        hasonguo:@雾中的影子高度现在是固定的你额
        雾中的影子:你是不是图片的高度不固定?用的wrap_content?

      本文标题:RecyclerView实现瀑布流的各种坑

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