Android项目总结——StaggeredGridLayoutManager 瀑布流常见问题

背景介绍:

在做一个项目的时候,有这么一个需求,需要请求服务器然后展现图片,
类似于一个美图展现的需求吧,具体项目细节就不进行细说了;
服务端采用的是:nodejs基于国内优秀的Thinkjs框架;
客户端:Android;
下面对Android客户端的问题进行归纳总结,后面再总结服务端的。

需求功能点如下:
1. 支持分页查询,上拉查找更多,有数据时显示转圈加载更多,无数据时进行提示;
2. 采用爆布流的方式进行显示,同时图片的高度随机;

整体思路
1. 采用oKHttp 第三方开源库进行接口数据请求;
2. 使用RecyclerView + StaggeredGridLayoutManager 进行爆布流的UI实现;

常见问题:
1.RecyclerView 如何实现下拉加载更多;
2.StaggeredGridLayoutManager 显示加载更多的时候,加载更多作为最后一个item没有单独占满屏幕宽度,只显示为一个item的宽度;
3.StaggeredGridLayoutManager 如何随机设置item的高度;
4.StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题;
5.RecyclerView莫名的Inconsistency detected崩溃;

开始填坑:
1. recyclerView 如何实现下拉加载更多:
思路:

  • 首先要能够监听recyclerView滑动事件;
  • 判断recyclerView是否滑动到最后一个item;
  • recyclerView 加载更多RecyclerView.Adapter的设置处理;

相关代码:
监听recyclerView滑动事件,并且计算得出是否滑动到最后一个item

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                staggeredGridLayoutManager.invalidateSpanAssignments(); //防止第一行到顶部有空白区域
                currentScrollState = newState;
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();
                if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && (lastVisibleItemPosition) >= totalItemCount - 1)) {
                    Log.i(TAG, "onScrollStateChanged: ...");
                    requestMoreData();//请求更多数据
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

                if (layoutManagerType == null) {
                    if (layoutManager instanceof LinearLayoutManager) {
                        layoutManagerType = LayoutManagerType.LinearLayout;
                    } else if (layoutManager instanceof GridLayoutManager) {
                        layoutManagerType = LayoutManagerType.GridLayout;
                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                        layoutManagerType = LayoutManagerType.StaggeredGridLayout;
                    } else {
                        throw new RuntimeException(
                                "Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
                    }
                }

                switch (layoutManagerType) {
                    case LinearLayout:
                        lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
                        break;
                    case GridLayout:
                        lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
                        break;
                    case StaggeredGridLayout:
                        StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                        if (lastPositions == null) {
                            lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                        }
                        staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
                        lastVisibleItemPosition = findMax(lastPositions);
                        break;
                    default:
                        break;
                }
            }
        });
    /**
     * 取数组中最大值
     *
     * @param lastPositions
     * @return
     */
    private int findMax(int[] lastPositions) {
        int max = lastPositions[0];
        for (int value : lastPositions) {
            if (value > max) {
                max = value;
            }
        }

        return max;
    }

    public static enum LayoutManagerType {
        LinearLayout,
        StaggeredGridLayout,
        GridLayout
    }

RecyclerView.Adapter的相关代码,主要定义两个ViewHolder类型,TYPE_BOTTOM 表示为底部的viewHolder,TYPE_NOMAL表示为正常的item的viewHolder,根据position的不同来显示不同的viewholder;
代码中还有对上面StaggeredGridLayoutManager的问题处理,具体的下面细说:

public class XXXRcyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_BOTTOM = 0;
    private static final int TYPE_NOMAL = 1;
    private LayoutInflater inflater;
    private Context mContext;
    private int preNumb = 0;
    private boolean isLoading = false;

    ************此处省略部分代码

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_BOTTOM){
            return new BottomViewHolder(inflater.inflate(R.layout.item_type_bottom,parent,false));
        }
        return new NomalViewHolder(inflater.inflate(R.layout.item_qr_code_say_rcy,parent,false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NomalViewHolder){
            NomalViewHolder nomalViewHolder = (NomalViewHolder) holder;
            *********此处省略部分代码***********
            ViewGroup.LayoutParams lp = nomalViewHolder.imgView.getLayoutParams();
            int scale = new Random().nextInt(10);
            if (scale < 5){
                scale = 9;
            }
            lp.height= (int) (UIUtil.dp2px(mContext,200) *0.1 * scale);
            lp.width= RecyclerView.LayoutParams.MATCH_PARENT;
            nomalViewHolder.imgView.setLayoutParams(lp);
            *********以上对每个Imageview设置随机高度*********

        }else if (holder instanceof  BottomViewHolder){
            BottomViewHolder bottomViewHolder = (BottomViewHolder) holder;
            if (isLoading){

              bottomViewHolder.bottomContainer.setVisibility(View.VISIBLE);
                bottomViewHolder.imgView.setVisibility(View.VISIBLE);
                bottomViewHolder.textView.setVisibility(View.VISIBLE);
                /***提示无更多数据***/
            }else{
                bottomViewHolder.bottomContainer.setVisibility(View.VISIBLE);
                bottomViewHolder.imgView.setVisibility(View.GONE);
                bottomViewHolder.textView.setVisibility(View.VISIBLE);
                /***提示无更多数据***/
            }
        }
    }

    @Override
    public int getItemCount() {
        return xxxxList.size() + 1;
    }


    /**
     * 重写viewType的返回值
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        if (position < qrCodeSayBeanList.size()){
            return TYPE_NOMAL;
        }
        return TYPE_BOTTOM;
    }


    /**
     * 解决 GridLayoutManager 加载更多时,加载动画最后一个item没有占满屏幕宽度问题
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return isFooter(position) ? gridManager.getSpanCount() : 1;
                }
            });
        }

    }


    /**
     * 解决 StaggeredGridLayoutManager 加载更多时,加载动画导致最后一个item没有占满屏幕宽度问题
     * @param holder
     */
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        if (isStaggeredGridLayout(holder)) {
            handleLayoutIfStaggeredGridLayout(holder, holder.getLayoutPosition());
        }
    }

    private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) {
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
            return true;
        }
        return false;
    }

    protected void handleLayoutIfStaggeredGridLayout(RecyclerView.ViewHolder holder, int position) {
        if ( isFooter(position)){
            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
            p.setFullSpan(true);
        }
    }

    /**
     * 是否为 底部
     * @param position
     * @return
     */
    private boolean isFooter(int position) {
        if (position == qrCodeSayBeanList.size()){
            return true;
        }
        return false;
    }


    /**
         * 底部的viewHolder
         */
    private static class BottomViewHolder extends RecyclerView.ViewHolder{

        private View bottomContainer;
        private ImageView imgView;
        private TextView textView;
        public BottomViewHolder(View itemView) {
            super(itemView);
            bottomContainer = itemView.findViewById(R.id.bottom_container);
            imgView = itemView.findViewById(R.id.img_loading);
            textView = itemView.findViewById(R.id.txt_tips);
        }
    }

    /**
     * 正常的viewHolder
     */
    private static class NomalViewHolder extends RecyclerView.ViewHolder{
        private ImageView imgView;
        private TextView txtViewNumb;
        private TextView txtLikedNumb;
        public NomalViewHolder(View itemView) {
            super(itemView);
            imgView = itemView.findViewById(R.id.img_view);
            txtViewNumb = itemView.findViewById(R.id.txt_view_numbs);
            txtLikedNumb = itemView.findViewById(R.id.txt_tap_liked_numbs);
        }
    }

    /**
     * 开始加载动画,局部刷新
     */
    public void startLoading(){
        isLoading = true;
//        notifyDataSetChanged();
        notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
        preNumb = xxxList.size();
    }

    /**
     * 停止加载动画,局部刷新
     */
    public void stopLoading(){
        isLoading = false;
//        notifyDataSetChanged();
        notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
        preNumb = xxxList.size();
    }

    /**
     * 停止加载动画,全局刷新
     */
    public void stopLoadingNotifyAll(){
        isLoading = false;
        notifyDataSetChanged();
    }
}

2.StaggeredGridLayoutManager 显示加载更多的时候,加载更多作为最后一个item没有单独占满屏幕宽度,只显示为一个item的宽度;

主要是根据不同的layoutManager的特点进行重写下面这两个方法,并且调用相关layoutManager的Span的设置方法:

final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return isFooter(position) ? gridManager.getSpanCount() : 1;
                }
            });
StaggeredGridLayoutManager.LayoutParams.setFullSpan(true);
/**
     * 解决 GridLayoutManager 加载更多时,加载动画最后一个item没有占满屏幕宽度问题
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return isFooter(position) ? gridManager.getSpanCount() : 1;
                }
            });
        }

    }


    /**
     * 解决 StaggeredGridLayoutManager 加载更多时,加载动画导致最后一个item没有占满屏幕宽度问题
     * @param holder
     */
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        if (isStaggeredGridLayout(holder)) {
            handleLayoutIfStaggeredGridLayout(holder, holder.getLayoutPosition());
        }
    }

    private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) {
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
            return true;
        }
        return false;
    }

    protected void handleLayoutIfStaggeredGridLayout(RecyclerView.ViewHolder holder, int position) {
        if ( isFooter(position)){
            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
            p.setFullSpan(true);
        }
    }

    /**
     * 是否为 底部
     * @param position
     * @return
     */
    private boolean isFooter(int position) {
        if (position == qrCodeSayBeanList.size()){
            return true;
        }
        return false;
    }

3.StaggeredGridLayoutManager 如何随机设置item的高度;

 ViewGroup.LayoutParams lp = nomalViewHolder.imgView.getLayoutParams();
            int scale = new Random().nextInt(10);
            if (scale < 5){
                scale = 9;
            }
            lp.height= (int) (UIUtil.dp2px(mContext,200) *0.1 * scale);
            lp.width= RecyclerView.LayoutParams.MATCH_PARENT;
            nomalViewHolder.imgView.setLayoutParams(lp);

4.StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题;
这里由于直接调用 notifyDataSetChanged();那么是全局刷新,而刷新的时候item的高度重新随机分配,导致数据刷新的时候会造成抖动。建议采用notifyItemRangeChanged进行局部刷新:

    /**
     * 开始加载动画,局部刷新
     */
    public void startLoading(){
        isLoading = true;
//        notifyDataSetChanged();
        notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
        preNumb = qrCodeSayBeanList.size();
    }

    /**
     * 停止加载动画,局部刷新
     */
    public void stopLoading(){
        isLoading = false;
//        notifyDataSetChanged();
        notifyItemRangeChanged(preNumb,xxxList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
        preNumb = xxxList.size();
    }

    /**
     * 停止加载动画,全局刷新
     */
    public void stopLoadingNotifyAll(){
        isLoading = false;
        notifyDataSetChanged();
    }

5.RecyclerView莫名的Inconsistency detected崩溃;
自定义一个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());
        }

    }
}

参考https://www.jianshu.com/p/2eca433869e9

猜你喜欢

转载自blog.csdn.net/u014132820/article/details/82708742