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

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 新闻app是一款基于Android平台的小型项目应用程序,它主要用于展示各类新闻内容,为用户提供便捷的阅读体验。该项目的源码包含了应用程序的基本框架和功能实现,方便开发者进行二次开发和定制。 新闻app的源码主要包含以下几个方面的内容: 1. 用户界面设计:源码中包含了新闻app的界面布局和样式,开发者可以根据自己的需要进行修改和美化。用户界面通常包括新闻列表、新闻详情页、分类标签等,开发者可以自由设计并添加其他功能模块。 2. 数据获取与展示:源码中实现了与服务器进行数据交互的功能,通过网络请求获取新闻数据,并在界面上展示出来。开发者可以根据需要修改数据请求接口和解析方式,实现与自己的服务器交互。 3. 新闻分类与搜索:源码中提供了新闻分类和搜索功能的实现,用户可以根据自己的兴趣和需求选择不同的新闻分类进行浏览,也可以通过搜索关键词进行精确定位。 4. 用户交互与分享:源码中包含了用户的登录注册功能和新闻内容的分享功能,用户可以通过登录账号进行个性化设置和收藏喜欢的新闻内容,也可以将新闻分享到社交媒体上与他人交流。 总之,新闻app源码是一个基础框架,开发者可以在此基础上进行二次开发和定制,根据自己的需求添加功能模块和美化界面,实现自己独特的新闻应用。 ### 回答2: Android新闻App是一个基于Android平台开发的小型项目,它的主要功能是提供最新的新闻内容给用户,并且用户可以进行浏览、搜索和分享等操作。下面是这个项目的一些关键特点和所需的源码组成部分: 1. 特点: - 用户界面友好,交互性强,提供舒适的浏览体验; - 支持实时更新,提供最新的新闻内容; - 具备搜索功能,方便用户查找感兴趣的新闻; - 支持新闻分享功能,方便用户将新闻分享给朋友; - 具备图文混排的能力,可以展示新闻的文字和图片。 2. 源码组成部分: - 主界面布局代码:定义了App的整体布局结构,包括顶部导航栏、底部工具栏和新闻显示区域等。 - 数据源代码:负责获取新闻数据,可以通过API接口获取最新的新闻内容,也可以从本地数据库获取已缓存的新闻数据。 - 新闻列表适配器代码:用于将新闻数据展示在界面上,包括标题、描述和图片等。 - 新闻详情界面代码:用于显示单篇新闻的详细内容,包括标题、正文和相关图片等。 - 搜索功能代码:实现了按关键字搜索新闻的功能,可以在已有的新闻数据中进行筛选。 - 分享功能代码:集成了社交媒体的分享SDK,方便用户将新闻内容分享给朋友。 - 图片加载和缓存代码:处理了新闻中的图片加载和本地缓存,提高了图片加载速度和用户体验。 通过以上的源码组成部分,可以完成一个基本的新闻App,用户可以在界面上浏览最新的新闻内容,进行搜索和分享操作。这个小项目可以帮助开发者理解Android开发框架和开发方式,提高编码能力和UI设计能力。 ### 回答3: 新闻app是基于Android平台开发的一个小型应用程序,可以提供用户各种最新的新闻资讯。以下是关于这个项目的源码介绍。 该项目源码主要由Java语言编写,使用了Android Studio作为开发工具。代码结构清晰,包含了主要的几个模块。 1. 用户界面模块:这个模块负责显示新闻列表和新闻详情等信息,主要包含布局文件和相应的逻辑代码。列表界面使用RecyclerView控件展示新闻列表,详情界面使用WebView展示新闻内容。 2. 网络请求模块:这个模块负责与后台服务器进行数据交互,使用了Android的HttpURLConnection类来发送请求和接收响应。请求参数可以根据实际需要进行修改,例如可以根据新闻类别进行请求。 3. 数据解析模块:这个模块负责解析从服务器返回的JSON格式的数据,转换成Java对象供应用程序使用。可以使用Android提供的JSON解析库,如Gson。 4. 数据存储模块:这个模块负责缓存新闻数据,以提高应用程序的响应速度。可以使用SharedPreferences或SQLite数据库来存储新闻数据。同时也可以使用图片缓存库,如Glide或Picasso来缓存新闻图片。 5. 用户交互模块:这个模块负责处理用户的交互行为,例如点击新闻列表项跳转到新闻详情界面,下拉刷新获取最新数据等。可以使用Android提供的相关控件和事件监听器来实现用户交互。 除了以上几个主要的模块,还可以根据需要添加其他功能,例如搜索栏、分享按钮等。 总体来说,这个新闻app的源码提供了一个完整的开发框架,初学者可以通过阅读和理解源码来学习Android应用程序的开发流程和一些常用技术。同时,也可以根据实际需求进行二次开发,添加新的功能和改进用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值