RecyclerView瀑布流优化方案探讨

12,188 阅读15分钟

目录介绍

  • 01.规则瀑布流实现
  • 02.不规则瀑布流实现
    • 2.1 实现方式
    • 2.2 遇到问题
  • 03.瀑布流上拉加载
  • 04.给瀑布流设置分割线
  • 05.自定义Manager崩溃
  • 06.如何避免刷新抖动
  • 07.为何有时出现跳动
  • 08.瀑布流图片优化
  • 09.onBindViewHolder优化
  • 10.瀑布流item点击事件优化
  • 11.Glide加载优化
  • 12.建议指定图片的宽高

欢迎同行探讨瀑布流极致优化方案

  • 如果同行看到这篇文章,有好的瀑布流优化方案,欢迎给出建议,或者给链接也可以。
  • 需求:
    • 瀑布流大概有10来中不同type的item视图,然后视图是根据动态设置宽高,服务器会返回比例
    • 瀑布流中item需要切割圆角
    • 目前使用glide加载图片
  • 产品说让参考抖音快手类的app,让瀑布流滑动效果特别流畅……但目前遇到问题是滑动十几页没什么问题,但是滑动三四十页的时候会出现卡顿。欢迎同行给出建议!

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:github.com/yangchong21…
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.规则瀑布流实现

  • 最简单规则瀑布流实现,下面这种是设置3列数据,然后组数据高度是相同的。是规则的瀑布流。
    adapter = new ImageAdapter(this);
    recyclerView.setAdapter(adapter);
    GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3);
    gridLayoutManager.setSpanSizeLookup(adapter.obtainGridSpanSizeLookUp(3));
    recyclerView.setLayoutManager(gridLayoutManager);
    SpaceViewItemLine itemDecoration = new SpaceViewItemLine(20);
    recyclerView.addItemDecoration(itemDecoration);
    

02.不规则瀑布流实现

  • 最简单的不规则瀑布流实现,下面这种是设置2列数据,然后数据的高度都不同,图片的高度随机。
    adapter = new ImageStageredAdapter(this);
    recyclerView.setAdapter(adapter);
    StaggeredGridLayoutManager staggeredGridLayoutManager =
            new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(staggeredGridLayoutManager);
    SpaceViewItemLine itemDecoration = new SpaceViewItemLine(20);
    recyclerView.addItemDecoration(itemDecoration);
    
  • 这里是伪代码。假设设置不同高度,代码如下。简单设置不同图片高度不同,这个是在onBindViewHolder中操作。
    ViewGroup.LayoutParams params = imgPicture.getLayoutParams();
    //假设有多种不同的类型
    int type = getAdapterPosition()%5;
    //计算View的高度
    int height = 300;
    switch (type){
        case 0:
            height = 500;
            break;
        case 1:
            height = 750;
            break;
        case 2:
            height = 880;
            break;
        case 3:
            height = 360;
            break;
        case 4:
            height = 660;
            break;
        default:
            break;
    }
    params.height = height;
    imgPicture.setLayoutParams(params);
    

2.2 遇到问题

  • 遇到问题
    • 1.RecyclerView 如何实现下拉加载更多;
    • 2.StaggeredGridLayoutManager 显示加载更多的时候,加载更多作为最后一个item没有单独占满屏幕宽度,只显示为一个item的宽度;
    • 3.StaggeredGridLayoutManager 如何随机设置item的高度;
    • 4.StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题;
    • 5.RecyclerView莫名的Inconsistency detected崩溃;
  • 卡顿和内存释放
    • 瀑布流滑动几页感觉还行,但是一旦滑动了三四十页,就感觉页面有些卡顿了。

03.瀑布流上拉加载

  • 首先添加监听方法,添加了这个方法就可以上拉加载更多数据呢。但是发现还有点问题,上拉加载更多的那个布局只是占1/spanCount列,这样显得特别难看。那么该如何处理呢,接着往下看……
    • 首先要能够监听recyclerView滑动事件;
    • 判断recyclerView是否滑动到最后一个item;
    • recyclerView 加载更多RecyclerView.Adapter的设置处理:RecyclerView.Adapter的相关代码,主要定义两个ViewHolder类型,footType表示为底部的viewHolder,normalType表示为正常的item的viewHolder,根据position的不同来显示不同的viewholder;
    // 实现上拉加载重要步骤,设置滑动监听器,RecyclerView自带的ScrollListener
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            // 在newState为滑到底部时
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                // 如果没有隐藏footView,那么最后一个条目的位置就比我们的getItemCount少1,自己可以算一下
                if (!adapter.isFadeTips() && lastVisibleItem + 1 == adapter.getItemCount()) {
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            updateRecyclerView(adapter.getRealLastPosition(),
                                    adapter.getRealLastPosition() + PAGE_COUNT);
                        }
                    }, 2500);
                }
    
                // 如果隐藏了提示条,我们又上拉加载时,那么最后一个条目就要比getItemCount要少2
                if (adapter.isFadeTips() && lastVisibleItem + 2 == adapter.getItemCount()) {
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            updateRecyclerView(adapter.getRealLastPosition(),
                                    adapter.getRealLastPosition() + PAGE_COUNT);
                        }
                    }, 2500);
                }
            }
        }
    
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            // 在滑动完成后,拿到最后一个可见的item的位置
            int positions[] = staggeredGridLayoutManager.findLastVisibleItemPositions(null);
            for(int pos : positions){
                if(pos > lastVisibleItem){
                    lastVisibleItem = pos;//得到最后一个可见的item的position
                }
            }
        }
    });
    
  • 因为是瀑布流,要设置footerView占据一行,这个需要这样设置,代码如下所示
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
            int position = holder.getLayoutPosition();
            //如果是上拉加载更多类型,则设置setFullSpan为true,那么它就会占一行
            if (getItemViewType(position) == footType) {
                params.setFullSpan(true);
            }
        }
    }
    

04.给瀑布流设置分割线

  • 先来看看出现错位,分割线出现问题的代码。下面这种方式根据childCount来判断奇数和偶数设置的不同间距。
    • 那么比如说当为奇数时,设置该item到左为20,到右为5;当为偶数时,该item到左为5,到右为20。
    • 如果奇数的item都在左边,偶数的item都在右边,那么间距就没有问题。
    • 如果第一个item在左边【高度很高】,第2个,第3个,第4个item都在右边,第5个在左边……那么思考一下,这个时候第3个item在右边,那么就会造成间距不规则。
    • 显然不能根据奇数或者偶数来设置item左右间距的大小的,会出现错位。
    recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                                   @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int position = parent.getChildAdapterPosition(view);
            int spanCount = 0;
            int spanIndex = 0;
            RecyclerView.Adapter adapter = parent.getAdapter();
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (adapter==null || layoutManager==null){
                return;
            }
            if (layoutManager instanceof StaggeredGridLayoutManager){
                spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
                spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
            }
            //普通Item的尺寸
            //TODO 会出现错位的问题
            int itemCount = adapter.getItemCount();
            int childCount = layoutManager.getChildCount();
            RefreshLogUtils.d("SpaceViewItemLine--count--"+itemCount + "-----"+childCount+"---索引--"+position+"---"+spanIndex);
            if (position<itemCount && spanCount==2) {
                if (childCount % 2 == 0){
                    //这个是右边item
                    outRect.left = 5;
                    outRect.right = 20;
                } else {
                    //这个是左边item
                    outRect.left = 20;
                    outRect.right = 5;
                }
                if (childCount==1 || childCount==2){
                    outRect.top = 0;
                } else {
                    outRect.top = 20;
                }
                RefreshLogUtils.d("SpaceViewItemLine--间距--"+childCount+"----"+outRect.left+"-----"+outRect.right);
            }
        }
    });
    
  • 解决办法,可以通过StaggeredGridLayoutManager.LayoutParams里的getSpanIndex()来判断,这个方法不管你高度怎样,他都是左右左右开始排列的。
    recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                                   @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int position = parent.getChildAdapterPosition(view);
            int spanCount = 0;
            int spanIndex = 0;
            RecyclerView.Adapter adapter = parent.getAdapter();
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (adapter==null || layoutManager==null){
                return;
            }
            if (layoutManager instanceof StaggeredGridLayoutManager){
                spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
                spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
            }
            //普通Item的尺寸
            int itemCount = adapter.getItemCount();
            int childCount = layoutManager.getChildCount();
            RefreshLogUtils.d("SpaceViewItemLine--count--"+itemCount + "-----"+childCount+"---索引--"+position+"---"+spanIndex);
            if (position<itemCount && spanCount==2) {
                if (spanIndex != GridLayoutManager.LayoutParams.INVALID_SPAN_ID) {
                    //getSpanIndex方法不管控件高度如何,始终都是左右左右返回index
                    if (spanIndex % 2 == 0) {
                        //这个是左边item
                        outRect.left = 20;
                        outRect.right = 5;
                    } else {
                        //这个是右边item
                        outRect.left = 5;
                        outRect.right = 20;
                    }
                    if (childCount==1 || childCount==2){
                        outRect.top = 0;
                    } else {
                        outRect.top = 20;
                    }
                }
                //outRect.top = space;
                RefreshLogUtils.d("SpaceViewItemLine--间距--"+spanIndex+"----"+outRect.left+"-----"+outRect.right);
            }
        }
    });
    

05.自定义Manager崩溃

  • RecyclerView莫名的Inconsistency detected崩溃;
    • 出现这个异常原因:
      • 使用RecyclerView加官方下拉刷新的时候,如果绑定的List对象在更新数据之前进行了clear,而这时用户紧接着迅速上滑RV,就会造成崩溃,而且异常不会报到你的代码上,属于RV内部错误。
    • 自定义一个CustomStaggeredGridLayoutManager 在onLayoutChildren对异常进行捕获:
    public class CustomStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
        private static final String TAG = "LOG_CustomStaggered";
        public CustomStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        public CustomStaggeredGridLayoutManager(int spanCount, int orientation) {
            super(spanCount, orientation);
        }
    
        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            try {
                super.onLayoutChildren(recycler, state);
            }catch (Exception e){
                Log.i(TAG, "onLayoutChildren: e " + e.getMessage());
            }
        }
    }
    
  • 关于StaggeredGridLayoutManager异常说明

06.如何避免刷新抖动

  • StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题; 这里由于直接调用 notifyDataSetChanged();那么是全局刷新,而刷新的时候item的高度重新随机分配,导致数据刷新的时候会造成抖动。建议采用notifyItemRangeChanged进行局部刷新:
    public void setDatas(List mDatas ) {
        this.dataList = mDatas;
        notifyDataSetChanged();
    }
    
    /**
     * 注意这个是未关注时候的瀑布流刷新数据
     * 上拉加载更多时,建议刷新上拉加载那一部分数据即可,不用刷新所有数据
     * @param puBuList                      瀑布流集合
     */
    public void setMoreData(List<BasePubuBean> puBuList) {
        int start = mayContentList.size();
        if (puBuList!=null && puBuList.size()!=0){
            mayContentList.addAll(puBuList);
            int end = mayContentList.size();
            mRecommendPuBuAdapter.notifyItemRangeInserted(start,end);
        }
    }
    

07.为何有时出现跳动

  • 由于我们加载的图片高度不确定(宽度确定因为可以根据屏幕宽度和每行Item数目进行等分),而当我们向RecyclerView下方滑动一段距离后,由于ViewHolder的回收机制,item的尺寸并不确定,滑回到上方时Item需要重新自行绘制,于是这个又导致重绘,所以会有闪烁、跳动、空白等问题。说到底,只要我们在重绘前确定了Item的尺寸,那么就可以避免Item去重新计算自己的尺寸,就可以避免重绘导致的诸多问题。

08.瀑布流图片优化

  • 具体优化方案
    • 第一步:减少布局嵌套,并且在拿到服务器的尺寸后,在onBindViewHolder中给图片控件设置宽高时,避免创建大量临时的LayoutParams对象
    • 第二步:使用glide加载,绑定activity或者fragment的生命周期,尽量不用用全局上下文或者静态上下文。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。
    • 第三步:对于list条目,尤其是瀑布流,不建议使用TransitionOptions来加载设置的动画,尤其是不要使用自己自定义的动画
  • 避免使用圆角的ImageView
    • 在实际项目内,经常会用到一些带圆角的图片,或者直接就是圆形的图片。圆形的图片,多数用于一些用户的头像之类的显示效果。
    • 而在 Android 下,也有大量的类似 XxxImageView 的开源控件,用于操作 Bitmap 以达到一个圆角图片的效果,例如 Github 上比较火的 RoundedImageView。
    • 它们大部分的原理,是接收到你传递的 Bitmap ,然后再输出一个与原来 Bitmap 等大的新 Bitmap ,在此基础之上,进行圆角的一些处理,这就导致了,实际上会在内存中,多持有一个 Bitmap ,一下一张图片占用的内存就被加倍了。
    • 所以既然已经选择使用Glide,推荐使用glide-transformations这个开源库配合使用,glide-transformations 利用 Glide 的 bitmapTransfrom() 接口,实现对加载的 Bitmap 的进行一些变换操作。glide-transformations提供一系类对加载的图片的变换操作,从形状变换到色彩变换,全部支持,基本上满足大部分开发需要,并且它会复用 Glide 的 BitmapPool ,来达到节约内存的目的。

09.onBindViewHolder优化

  • 在这个方法中,主要是做数据绑定到View视图。由于瀑布流会有多种不同type类型的视图,有些需要设置宽高,有的则要从服务器拿到宽高数据然后动态修改视图属性。因此关于宽高的计算其实还可以做一些优化。
  • 先来看一下最初的代码,这里只展示动态设置宽高的代码。如下所示
    • 看了下面代码,会发现这些问题。第一,频繁计算瀑布流中不同类型的宽度,该宽度是屏幕宽度减去间距,然后是除以2,瀑布流为两行。第二,频繁创建LayoutParams对象,然后设置宽高的属性。
    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        int width = 0;
        int height = 0;
        switch (holder.getItemViewType()) {
            case BasePubuBean.content_type9:
                break;
            //发布的短视频的宽高也是限制为3:4
            case BasePubuBean.content_type2:
                int videoWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
                int videoHeight = (int) (videoWidth * (4/3.0f));
                RelativeLayout.LayoutParams puBuParams2 = ViewUtils.getPuBuParams(mContext, videoWidth, videoHeight);
                holder.getView(R.id.mImageView).setLayoutParams(puBuParams2);
                break;
            //活动头图,自适应拿到图片宽度,然后宽高比是4:3
            case BasePubuBean.content_type4:
                int imageWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
                int imageHeight = (int) (imageWidth * (3/4.0f));
                RelativeLayout.LayoutParams puBuParams4 = ViewUtils.getPuBuParams(mContext,imageWidth, imageHeight);
                holder.getView(R.id.mImageView).setLayoutParams(puBuParams4);
                break;
            //推荐车系,宽高比是4:3
            case BasePubuBean.content_type10:
                int imageWidth10 = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
                int imageHeight10 = (int) (imageWidth10 * (3/4.0f));
                ImageView imageView = holder.getView(R.id.mImageView);
                ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
                layoutParams.height = imageHeight10;
                layoutParams.width = imageWidth10;
                imageView.setLayoutParams(layoutParams);
                break;
            case BasePubuBean.content_type1:
            case BasePubuBean.content_type3:
            case BasePubuBean.content_type5:
            case BasePubuBean.content_type6:
            case BasePubuBean.content_type7:
            case BasePubuBean.content_type8:
                int imageWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
                width = mData.get(position).getWidth();
                height =  mData.get(position).getHeight();
                if( width!=0 && height!=0){
                     RelativeLayout.LayoutParams   puBuParams1 = ViewUtils.getPuBuParams(mContext,imageWidth, height);
                    holder.getView(R.id.mImageView).setLayoutParams(puBuParams1);
                }else {
                    int video_width = mData.get(position).getVideo_width();
                    int video_height =  mData.get(position).getVideo_height();
                    if(video_width!=0 && video_height!=0){
                        holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
                                mContext,imageWidth, video_height));
                    }else {
                        holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
                                mContext,imageWidth , 125));
                    }
                }
                break;
            default:
                break;
        }
    }
    
  • 然后看一下优化后的代码。
    • 那么下面这种代码,就可以极大减少频繁的动态计算宽高等属性。通过imageView34.getLayoutParams()方式获取layoutParams,就可以避免上面那种通过new创建大量的对象。要知道,平时像上面代码那么用也没问题,但是在recyclerView瀑布流中,也可以细微优化一下。
    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        //获取屏幕宽度
        if (screenWidth==0){
            screenWidth = SysUtils.getScreenWidth((Activity) mContext);
        }
        //16+11+16
        if (padding==0){
            padding = SysUtils.Dp2Px(mContext,43);
        }
        if (imageWidth==0){
            imageWidth = (screenWidth-padding) / 2;
        }
        //宽高为3:4的图片高度
        if (imageHeight34 ==0){
            imageHeight34 = (int) (imageWidth * (4/3.0f));
        }
        //宽高4:3的图片高度
        if (imageHeight43 ==0){
            imageHeight43 = (int) (imageWidth * (3/4.0f));
        }
        //长宽为16:9的图片高度
        if (imageHeight169 ==0){
            imageHeight169 = (int) (imageWidth * (16/9.0f));
        }
        switch (holder.getItemViewType()) {
            case BasePubuBean.content_type9:
                break;
            //发布的短视频的是限制为3:4
            case BasePubuBean.content_type2:
                int width34 = imageWidth;
                int height34 = imageHeight34;
                ImageView imageView34 = holder.getView(R.id.mImageView);
                ViewGroup.LayoutParams layoutParams34 = imageView34.getLayoutParams();
                layoutParams34.height = height34;
                layoutParams34.width = width34;
                imageView34.setLayoutParams(layoutParams34);
                break;
            //行情文章也是4:3
            case BasePubuBean.content_type8:
            //活动头图,自适应拿到图片宽度,然后是4:3
            case BasePubuBean.content_type4:
            //文章详情也是4:3
            case BasePubuBean.content_type5:
            //推荐车系是4:3
            case BasePubuBean.content_type10:
                int width43 = imageWidth;
                int height43 = imageHeight43;
                ImageView imageView43 = holder.getView(R.id.mImageView);
                ViewGroup.LayoutParams layoutParams43 = imageView43.getLayoutParams();
                layoutParams43.height = height43;
                layoutParams43.width = width43;
                imageView43.setLayoutParams(layoutParams43);
                break;
            //长视频16:9
            case BasePubuBean.content_type3:
                int width169 = imageWidth;
                int height169 = imageHeight169;
                ImageView imageView169= holder.getView(R.id.mImageView);
                ViewGroup.LayoutParams layoutParams169 = imageView169.getLayoutParams();
                layoutParams169.height = height169;
                layoutParams169.width = width169;
                imageView169.setLayoutParams(layoutParams169);
                break;
            case BasePubuBean.content_type1:
            case BasePubuBean.content_type6:
            case BasePubuBean.content_type7:
                ImageView imageView= holder.getView(R.id.mImageView);
                int width = mData.get(position).getWidth();
                int height =  mData.get(position).getHeight();
                if( width!=0 && height!=0){
                    //这种方式会创建大量的对象
                    //RelativeLayout.LayoutParams puBuParams1 = ViewUtils.getPuBuParams(mContext,width, height);
                    //holder.getView(R.id.mImageView).setLayoutParams(puBuParams1);
                    ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
                    layoutParams.height = height;
                    layoutParams.width = imageWidth;
                    imageView.setLayoutParams(layoutParams);
                }else {
                    int videoWidth = mData.get(position).getVideo_width();
                    int videoHeight =  mData.get(position).getVideo_height();
                    if(videoWidth!=0 && videoHeight!=0){
                        ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
                        layoutParams.height = videoHeight;
                        layoutParams.width = imageWidth;
                        imageView.setLayoutParams(layoutParams);
                        //holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
                        //        mContext,imageWidth, video_height));
                    }else {
                        ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
                        layoutParams.height = 125;
                        layoutParams.width = imageWidth;
                        imageView.setLayoutParams(layoutParams);
                        //holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
                        //        mContext,imageWidth,125));
                    }
                }
                break;
            default:
                break;
        }
    }
    

10.瀑布流item点击事件优化

  • 关于rv设置item条目点击事件有两种方式:1.在onCreateViewHolder中写;2.在onBindViewHolder中写;3.在ViewHolder中写。那么究竟是哪一种好呢?
    • 1.在onCreateViewHolder中写
      @NonNull
      @Override
      public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
          final View view = LayoutInflater.from(mContext).inflate(R.layout.item_me_gv_grid, parent, false);
          final MyViewHolder holder = new MyViewHolder(view);
          view.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  if (listener != null) {
                      listener.onItemClick(view, holder.getLayoutPosition());
                  }
              }
          });
          return holder;
      }
      
    • 2.在onBindViewHolder中写
      @Override
      public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
          holder.itemView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  if (listener != null) {
                      listener.onItemClick(holder.itemView, holder.getAdapterPosition());
                  }
              }
          });
      }
      
    • 3.在ViewHolder中写
      class MyViewHolder extends RecyclerView.ViewHolder {
          MyViewHolder(final View itemView) {
              super(itemView);
              itemView.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      if (listener != null) {
                          listener.onItemClick(itemView, getAdapterPosition());
                      }
                  }
              });
          }
      }
      
  • onBindViewHolder() 中频繁创建新的 onClickListener 实例没有必要,建议实际开发中应该在 onCreateViewHolder() 中每次为新建的 View 设置一次就行。

11.Glide加载优化

  • glide优化方案代码,滑动时候禁止加载图片,停止滑动后开始恢复加载图片。
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                LogUtil.i("yc---initRecyclerView"+ "恢复Glide加载图片");
                Glide.with(RecommendFragment.this).resumeRequests();
            }else {
                LogUtil.i("yc---initRecyclerView"+"禁止Glide加载图片");
                Glide.with(RecommendFragment.this).pauseRequests();
            }
        }
    });
    
  • 绑定控件的生命周期
    • 当一个界面离开之后,我们更希望当前的图片取消加载,那么 Glide 是怎么做到的呢?当在recyclerView列表中,item从可见滑动到不可见的时候,如何控制图片请求的生命周期,那么可以与控件生命周期相绑定。
    Glide.with(mImageView)
            .load(imgUrl)
            .apply(RequestOptions.bitmapTransform(multiTransformation)
                    .placeholder(R.drawable.glide_load)
                    .error(R.drawable.glide_error))
            .into(mImageView);
    
  • 低内存的情况下主动清除缓存,看最新版本glide,其实源码中以及处理了下面相关的逻辑。
    /**
     * 低内存的时候执行
     */
    @Override
    public void onLowMemory() {
        Log.d("Application", "onLowMemory");
        super.onLowMemory();
        Glide.get(this).clearMemory();
    }
    
    
    /**
     * HOME键退出应用程序
     * 程序在内存清理的时候执行
     */
    @Override
    public void onTrimMemory(int level) {
        Log.d("Application", "onTrimMemory");
        super.onTrimMemory(level);
        if (level == TRIM_MEMORY_UI_HIDDEN){
            Glide.get(this).clearMemory();
        }
        Glide.get(this).trimMemory(level);
    }
    

12.建议指定图片的宽高

  • Glide设置图片控件wrap_content不建议支持的问题
    • 官方说,不支持并且不建议imageview设置wrap_content。因为这样glide不知道要加载多大的图片给我们才好,在他的接口(Sizes and dimensions)中也有体现。普通的imageview其实也还好,如果放在列表(RecyclerView)中, 由于我们并不知道目标图片大小是多大的,所以我们选择了wrap_content,那么在上下来回滚动过程中,就会导致图片一会大一会小的bug.
  • 官方 issue 作者回答如下:
    • 所以,如果可以,那么还是指定图片控件的宽高吧!
    Don't use wrap_content.
    
    Even if you don't use Glide, wrap_content necessarily means that the size of your views in RecyclerView are going to change from item to item. That's going to cause all sorts of UI weirdness.
    
    One option is to try to obtain the image dimensions in whatever metadata you're using to populate the RecyclerView. Then you can set a fixed View size in onBindViewHolder so the view size at least doesn't change when the image is loaded. You're still likely to see weird scroll bar behavior though.
    
    If nothing else, you can always pick a uniform size that's large enough for all items and use the same consistent size for every item.
    
    For the image file size, you can downscale or upscale by setting the ImageView size manually to 150dp x 150dp. Ultimately either you need uniform view sizes or predetermined view sizes. There's nothing else that will prevent content from expanding or shrinking in your RecyclerView.
    
    For the placeholder bit, I think that will be fixed by 648c58e, you can check by trying the 4.2.0-SNAPSHOT version: http://bumptech.github.io/glide/dev/snapshots.html.
    

01.关于博客汇总链接

02.关于我的博客

业余demo链接:github.com/yangchong21…