天天看點

複雜type頁面封裝庫,支援多種狀态切換和下拉重新整理上拉加載

目錄介紹

  • 1.複雜頁面庫介紹
  • 2.本庫優勢亮點
    • 2.1 支援多種狀态切換管理
    • 2.2 支援添加多個header和footer
    • 2.3 支援側滑功能和拖拽移動
    • 2.4 其他亮點介紹
  • 3.如何使用介紹
    • 3.1 最基礎的使用
    • 3.2 添加下拉重新整理和加載更多監聽
    • 3.3 添加header和footer操作
    • 3.4 設定資料和重新整理
    • 3.5 設定adapter
    • 3.6 設定條目點選事件
    • 3.7 設定側滑删除功能[QQ側滑删除]
    • 3.8 輕量級拖拽排序與滑動删除
  • 4.關于狀态切換
    • 4.1 關于布局内容
    • 4.2 關于實作思路
    • 4.3 關于狀态切換api調用
    • 4.4 關于自定義狀态布局
    • 4.5 關于自定義布局互動事件處理
  • 5.常用api介紹
    • 5.1 狀态切換方法說明
    • 5.2 viewHolder方法說明
    • 5.3 adapter方法說明
    • 5.4 分割線方法說明
    • 5.5 swipe側滑方法說明
    • 5.6 其他api說明
  • 6.優化實作點
    • 6.1 viewHolder優化
    • 6.2 狀态管理器優化
    • 6.3 recyclerView滑動卡頓優化
    • 6.4 多線程下插入資料優化
    • 6.5 rv四級緩存
    • 6.6 異常情況下儲存狀态
  • 7.實作效果展示
  • 8.版本更新說明
  • 9.參考資料說明
  • 10.其他内容介紹

開源庫位址: https://github.com/yangchong211/YCRefreshView

  • 自定義支援上拉加載更多,下拉重新整理,可以自定義頭部和底部,可以添加多個header,使用一個原生recyclerView就可以搞定複雜界面。支援自由切換狀态【加載中,加載成功,加載失敗,沒網絡等狀态】的控件,可以自定義狀态視圖View。拓展功能【支援長按拖拽,側滑删除】,輕量級,可以選擇性添加 。持續更新……

  • 自定義支援上拉加載更多【加載中,加載失敗[比如沒有更多資料],加載異常[無網絡],加載成功等多種狀态】,下拉重新整理,可以實作複雜的狀态頁面,支援自由切換狀态【加載中,加載成功,加載失敗,沒網絡等狀态】的控件,拓展功能[支援長按拖拽,側滑删除]可以選擇性添加。具體使用方法,可以直接參考demo案例。
  • 支援複雜type頁面,例如添加自定義頭部HeaderView和底部布局FooterView,支援橫向滑動list,還可以支援粘貼頭部list[類似微信好友分組],支援不規則瀑布流效果,支援側滑删除功能。

  • 支援在布局中或者代碼設定自定義不同狀态的view,一行代碼既可以切換不同的狀态,十分友善。
  • 針對自定義狀态view或者layout,可以實作互動功能,比如當切換到網絡異常的頁面,可以點選頁面控件取重新整理資料或者跳轉設定網絡頁面

  • 支援添加多個自定義header頭部布局,可以自定義footer底部布局。十分友善實作複雜type的布局頁面,結構上層次分明,便于維護。
  • 支援複雜界面使用,比如有的頁面包含有輪播圖,按鈕組合,橫向滑動list,還有複雜list,那麼用這個控件就可以搞定。

  • 輕量級側滑删除菜單,直接嵌套item布局即可使用,使用十分簡單
  • 通過自定義ItemTouchHelper實作RecyclerView條目Item拖拽排序,隻是設定是否拖拽,設定拖拽item的背景顔色,優化了拖拽效果,比如在拖拽過程中設定item的縮放和漸變效果

  • 支援上拉加載,下拉重新整理。當上拉加載更多失敗或者異常時,可以設定自定義加載更多失敗或者異常布局(比如沒有網絡時的場景),同時點選該異常或者失敗布局可以恢複加載更多資料;當上拉加載更多沒有更多資料時,可以設定自定義加載更多無資料布局。
  • 可以設定上拉加載更多後自動加載下一頁資料,也可以上拉加載更多後手動觸發加載下一頁資料。在上拉加載更多時,可以設定加載更多的布局,支援加載監聽。
  • 支援粘貼頭部的需求效果,這種效果類似微信好友分組的那種功能界面。
  • 支援插入【插入指定索引】,更新【更新指定索引或者data資料】或者删除某條資料,支援删除所有資料。同時在多線程條件下,添加了鎖機制避免資料錯亂!
  • 支援橫向滑動list效果,支援瀑布流的效果,還支援與CoordinatorLayout結合實作炫酷的效果。這種效果特别不錯……
  • 已經用于實際開發項目投資界,新芽,沙丘大學中……且經過近三年時間的疊代與維護,持續更新維護中!

  • 首先在內建:
    • implementation 'org.yczbj:YCRefreshViewLib:2.5.8'
  • 在布局中
    <org.yczbj.ycrefreshviewlib.YCRefreshView
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_progress="@layout/view_custom_loading_data"
        app:layout_empty="@layout/view_custom_empty_data"
        app:layout_error="@layout/view_custom_data_error"/>           
  • 在代碼中,初始化recyclerView
    adapter = new PersonAdapter(this);
    recyclerView.setAdapter(adapter);
    final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(linearLayoutManager);
    adapter.addAll(data);           
  • 在代碼中,建立adapter實作RecyclerArrayAdapter
    public class PersonAdapter extends RecyclerArrayAdapter<PersonData> {
    
        public PersonAdapter(Context context) {
            super(context);
        }
    
        @Override
        public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
            return new PersonViewHolder(parent);
        }
    
        public class PersonViewHolder extends BaseViewHolder<PersonData> {
    
            private ImageView iv_news_image;
    
            PersonViewHolder(ViewGroup parent) {
                super(parent, R.layout.item_news);
                iv_news_image = getView(R.id.iv_news_image);
            }
    
            @Override
            public void setData(final PersonData person){
    
            }
        }
    }           

  • 下拉重新整理監聽操作
    //設定重新整理listener
    recyclerView.setRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            //重新整理操作
        }
    });
    //設定是否重新整理
    recyclerView.setRefreshing(false);
    //設定重新整理顔色
    recyclerView.setRefreshingColorResources(R.color.colorAccent);           
  • 上拉加載更多監聽操作
    • 第一種情況,上拉加載更多後自動加載下一頁資料
    //設定上拉加載更多時布局,以及監聽事件
    adapter.setMore(R.layout.view_more, new OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            //可以做請求下一頁操作
        }
    });           
    • 第二種情況,上拉加載更多後手動觸發加載下一頁資料
    adapter.setMore(R.layout.view_more2, new OnMoreListener() {
        @Override
        public void onMoreShow() {
            //不做處理
        }
    
        @Override
        public void onMoreClick() {
            //點選觸發加載下一頁資料
        }
    });           
  • 在上拉加載更多時,可能出現沒有更多資料,或者上拉加載失敗,該如何處理呢?
    //設定上拉加載沒有更多資料監聽
    adapter.setNoMore(R.layout.view_no_more, new OnNoMoreListener() {
        @Override
        public void onNoMoreShow() {
            //上拉加載,沒有更多資料展示,這個方法可以暫停或者停止加載資料
            adapter.pauseMore();
        }
    
        @Override
        public void onNoMoreClick() {
            //這個方法是點選沒有更多資料展示布局的操作,比如可以做吐司等等
            Log.e("逗比","沒有更多資料了");
        }
    });
    //設定上拉加載更多異常監聽資料監聽
    adapter.setError(R.layout.view_error, new OnErrorListener() {
        @Override
        public void onErrorShow() {
            //上拉加載,加載更多資料異常展示,這個方法可以暫停或者停止加載資料
            adapter.pauseMore();
        }
    
        @Override
        public void onErrorClick() {
            //這個方法是點選加載更多資料異常展示布局的操作,比如恢複加載更多等等
            adapter.resumeMore();
        }
    });           

  • 添加headerView操作。至于添加footerView的操作,幾乎和添加header步驟是一樣的。
    • 添加普通的布局【非listView或者RecyclerView布局】
    adapter.addHeader(new InterItemView() {
        @Override
        public View onCreateView(ViewGroup parent) {
            View inflate = LayoutInflater.from(context).inflate(R.layout.header_view, null);
            return inflate;
        }
    
        @Override
        public void onBindView(View headerView) {
            TextView tvTitle = headerView.findViewById(R.id.tvTitle);
        }
    });           
    • 添加list布局【以橫向recyclerView為例子】
    adapter.addHeader(new InterItemView() {
        @Override
        public View onCreateView(ViewGroup parent) {
            RecyclerView recyclerView = new RecyclerView(parent.getContext()){
                //為了不打擾橫向RecyclerView的滑動操作,可以這樣處理
                @SuppressLint("ClickableViewAccessibility")
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    super.onTouchEvent(event);
                    return true;
                }
            };
            return recyclerView;
        }
    
        @Override
        public void onBindView(View headerView) {
            //這裡的處理别忘了
            ((ViewGroup)headerView).requestDisallowInterceptTouchEvent(true);
        }
    });           
  • 注意要點
    • 如果添加了HeaderView,凡是通過ViewHolder拿到的position都要減掉HeaderView的數量才能得到正确的position。

  • 添加所有資料,可以是集合,也可以是數組
    //添加所有資料
    adapter.addAll(data);
    //添加單挑資料
    adapter.add(data);           
  • 插入,重新整理和删除資料
    //插入指定索引資料,單個資料
    adapter.insert(data, pos);
    //插入指定索引資料,多個資料
    adapter.insertAll(data, pos);
    //重新整理指定索引資料
    adapter.update(data, pos);
    //删除資料,指定資料
    adapter.remove(data);
    //删除資料,指定索引
    adapter.remove(pos);
    //清空所有資料           

  • 注意自定義adapter需要實作RecyclerArrayAdapter,其中T是泛型,就是你要使用的bean資料類型
    public class PersonAdapter extends RecyclerArrayAdapter<PersonData> {
    
        public PersonAdapter(Context context) {
            super(context);
        }
    
        @Override
        public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
            return new PersonViewHolder(parent);
        }
    
        public class PersonViewHolder extends BaseViewHolder<PersonData> {
    
            private TextView tv_title;
            private ImageView iv_news_image;
    
            PersonViewHolder(ViewGroup parent) {
                super(parent, R.layout.item_news);
                iv_news_image = getView(R.id.iv_news_image);
                tv_title = getView(R.id.tv_title);
    
                //添加孩子的點選事件
                addOnClickListener(R.id.iv_news_image);
                addOnClickListener(R.id.tv_title);
            }
    
            @Override
            public void setData(final PersonData person){
                Log.i("ViewHolder","position"+getDataPosition());
                tv_title.setText(person.getName());
            }
        }
    }           

  • 條目單擊點選事件,長按事件[省略,可以自己看代碼]
    adapter.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(int position) {
    if (adapter.getAllData().size()>position && position>=0){
        //處理點選事件邏輯
    }
    }
    });           
  • 條目中孩子的點選事件
    //添加孩子的點選事件,可以看3.5設定adapter
    addOnClickListener(R.id.iv_news_image);
    addOnClickListener(R.id.tv_title);
    
    //設定孩子的點選事件
    adapter.setOnItemChildClickListener(new OnItemChildClickListener() {
    @Override
    public void onItemChildClick(View view, int position) {
    switch (view.getId()){
        case R.id.iv_news_image:
            Toast.makeText(HeaderFooterActivity.this,
                    "點選圖檔了",Toast.LENGTH_SHORT).show();
            break;
        case R.id.tv_title:
            Toast.makeText(HeaderFooterActivity.this,
                    "點選标題",Toast.LENGTH_SHORT).show();
            break;
        default:
            break;
    }
    }
    });           

  • 在布局檔案中,這裡省略部分代碼
    <org.yczbj.ycrefreshviewlib.swipeMenu.YCSwipeMenu
        android:orientation="horizontal">
        <!--item内容-->
        <RelativeLayout
        </RelativeLayout>
    
        <!-- 側滑菜單 -->
        <Button
            android:id="@+id/btn_del"/>
        <Button
            android:id="@+id/btn_top"/>
    </org.yczbj.ycrefreshviewlib.swipeMenu.YCSwipeMenu>           
  • 在代碼中設定
    • 在adapter中定義接口
    private OnSwipeMenuListener listener;
    public void setOnSwipeMenuListener(OnSwipeMenuListener listener) {
        this.listener = listener;
    }           
    • 在adapter設定點選事件
    View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_del:
                    if (null != listener) {
                        listener.toDelete(getAdapterPosition());
                    }
                    break;
                case R.id.btn_top:
                    if (null != listener) {
                        listener.toTop(getAdapterPosition());
                    }
                    break;
            }
        }
    };
    btn_del.setOnClickListener(clickListener);
    btn_top.setOnClickListener(clickListener);           
  • 處理置頂或者删除的功能
    adapter.setOnSwipeMenuListener(new OnSwipeMenuListener() {
        //删除功能
        @Override
        public void toDelete(int position) {
    
        }
    
        //置頂功能
        @Override
        public void toTop(int position) {
    
        }
    });           

  • 處理長按拖拽,滑動删除的功能。輕量級,自由選擇是否實作。
    mCallback = new DefaultItemTouchHelpCallback(new DefaultItemTouchHelpCallback
                    .OnItemTouchCallbackListener() {
    @Override
    public void onSwiped(int adapterPosition) {
    // 滑動删除的時候,從資料庫、資料源移除,并重新整理UI
    }
    
    @Override
    public boolean onMove(int srcPosition, int targetPosition) {
    return false;
    }
    });
    mCallback.setDragEnable(true);
    mCallback.setSwipeEnable(true);
    mCallback.setColor(this.getResources().getColor(R.color.colorAccent));
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallback);
    itemTouchHelper.attachToRecyclerView(recyclerView);           

  • YCRecyclerView是一個組合自定義控件,其布局如下所示
    <!--重新整理控件    省略部分代碼-->
    <android.support.v4.widget.SwipeRefreshLayout>
        <FrameLayout>
            <!--RecyclerView控件-->
            <android.support.v7.widget.RecyclerView/>
            <!--加載資料為空時的布局-->
            <FrameLayout/>
            <!--正在加載資料中的布局-->
            <FrameLayout/>
            <!--加載錯誤時的布局:網絡錯誤或者請求資料錯誤-->
            <FrameLayout/>
        </FrameLayout>
    </android.support.v4.widget.SwipeRefreshLayout>           

  • 關于頁面狀态切換的思路
    • 第一種方式:直接把這些界面include到main界面中,然後動态去切換界面,後來發現這樣處理不容易複用到其他項目中,而且在activity中處理這些狀态的顯示和隐藏比較亂
    • 第二種方式:利用子類繼承父類特性,在父類中寫切換狀态,但有些界面如果沒有繼承父類,又該如何處理
  • 而本庫采用的做法思路
    • 一個幀布局FrameLayout裡寫上4種不同類型布局,正常布局,空布局,加載loading布局,錯誤布局[網絡異常,加載資料異常]
    • 當然也可以自定義這些狀态的布局,通過addView的形式,将不同狀态布局添加到對應的FrameLayout中。而切換狀态,隻需要設定布局展示或者隐藏即可。

  • 如下所示
    //設定加載資料完畢狀态
    recyclerView.showRecycler();
    //設定加載資料為空狀态
    recyclerView.showEmpty();
    //設定加載錯誤狀态
    recyclerView.showError();
    //設定加載資料中狀态
    recyclerView.showProgress();           

  • //設定空狀态頁面自定義布局
    recyclerView.setEmptyView(R.layout.view_custom_empty_data);
    recyclerView.setEmptyView(view);
    //擷取空頁面自定義布局
    View emptyView = recyclerView.getEmptyView();
    
    //設定異常狀态頁面自定義布局
    recyclerView.setErrorView(R.layout.view_custom_data_error);
    recyclerView.setErrorView(view);
    
    //設定加載loading狀态頁面自定義布局
    recyclerView.setProgressView(R.layout.view_progress_loading);
    recyclerView.setProgressView(view);           

  • 有時候,加載頁面出現異常情況,比如沒有網絡會顯示自定義的網絡異常頁面。現在需要點選異常頁面按鈕等等操作,那麼該如何做呢?
    //注意需要
    LinearLayout ll_error_view = (LinearLayout) recyclerView.findViewById(R.id.ll_error_view);
    ll_error_view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    //比如,跳轉到網絡設定頁面,或者再次重新整理資料,或者其他操作等等
    }
    });           

  • 狀态切換方法說明
    //設定加載資料完畢狀态
    recyclerView.showRecycler();
    //設定加載資料為空狀态
    recyclerView.showEmpty();
    //設定加載錯誤狀态
    recyclerView.showError();
    //設定加載資料中狀态
    recyclerView.showProgress();
    //設定自定義布局,其他幾個方法同理
    recyclerView.setEmptyView(R.layout.view_custom_empty_data);           
  • viewHolder方法說明
    //子類設定資料方法
    setData方法
    //findViewById方式
    iv_news_image = getView(R.id.iv_news_image);
    //擷取上下文
    Context context = getContext();
    //擷取資料索引的位置
    int dataPosition = getDataPosition();
    //添加item中子控件的點選事件
    addOnClickListener(R.id.tv_title);           
  • adapter方法說明
    //删除索引處的資料
    adapter.remove(0);
    //觸發清空所有資料
    adapter.removeAll();
    //添加資料,注意這個是在最後索引處添加
    adapter.add(new PersonData());
    //添加所有資料
    adapter.addAll(DataProvider.getPersonList(0));
    //插入資料
    adapter.insert(data,3);
    //在某個索引處插入集合資料
    adapter.insertAll(data,3);
    //擷取item索引位置
    adapter.getPosition(data);
    //觸發清空所有的資料
    adapter.clear();
    //擷取所有的資料
    adapter.getAllData();
    
    //清除所有footer
    adapter.removeAllFooter();
    //清除所有header
    adapter.removeAllHeader();
    //添加footerView
    adapter.addFooter(view);
    //添加headerView
    adapter.addHeader(view);
    //移除某個headerView
    adapter.removeHeader(view);
    //移除某個footerView
    adapter.removeFooter(view);
    //擷取某個索引處的headerView
    adapter.getHeader(0);
    //擷取某個索引處的footerView
    adapter.getFooter(0);
    //擷取footer的數量
    adapter.getFooterCount();
    //擷取header的數量
    adapter.getHeaderCount();
    
    //設定上拉加載更多的自定義布局和監聽
    adapter.setMore(R.layout.view_more,listener);
    //設定上拉加載更多的自定義布局和監聽
    adapter.setMore(view,listener);
    //設定上拉加載沒有更多資料布局
    adapter.setNoMore(R.layout.view_nomore);
    //設定上拉加載沒有更多資料布局
    adapter.setNoMore(view);
    //設定上拉加載沒有更多資料監聽
    adapter.setNoMore(R.layout.view_nomore,listener);
    //設定上拉加載異常的布局
    adapter.setError(R.layout.view_error);
    //設定上拉加載異常的布局
    adapter.setError(view);
    //設定上拉加載異常的布局和異常監聽
    adapter.setError(R.layout.view_error,listener);
    //暫停上拉加載更多
    adapter.pauseMore();
    //停止上拉加載更多
    adapter.stopMore();
    //恢複上拉加載更多
    adapter.resumeMore();
    
    //擷取上下文
    adapter.getContext();
    //應該使用這個擷取item個數
    adapter.getCount();
    //設定操作資料[增删改查]後,是否重新整理adapter
    adapter.setNotifyOnChange(true);
    
    //設定孩子點選事件
    adapter.setOnItemChildClickListener(listener);
    //設定條目點選事件
    adapter.setOnItemClickListener(listener);
    //設定條目長按事件
    adapter.setOnItemLongClickListener(listener);           
  • 分割線方法說明
    //可以設定線條顔色和寬度的分割線
    //四個參數,上下文,方向,線寬,顔色
    final RecycleViewItemLine line = new RecycleViewItemLine(this, LinearLayout.HORIZONTAL,
            (int)AppUtils.convertDpToPixel(1,this),
            this.getResources().getColor(R.color.color_f9f9f9));
    recyclerView.addItemDecoration(line);
    
    //适用于瀑布流中的間距設定
    SpaceViewItemLine itemDecoration = new SpaceViewItemLine(
            (int) AppUtils.convertDpToPixel(8,this));
    itemDecoration.setPaddingEdgeSide(true);
    itemDecoration.setPaddingStart(true);
    itemDecoration.setPaddingHeaderFooter(true);
    recyclerView.addItemDecoration(itemDecoration);
    
    //可以設定線條顔色和寬度,并且可以設定距離左右的間距
    DividerViewItemLine itemDecoration = new
            DividerViewItemLine( this.getResources().getColor(R.color.color_f9f9f9)
            , LibUtils.dip2px(this, 1f),
            LibUtils.dip2px(this, 72), 0);
    itemDecoration.setDrawLastItem(false);
    recyclerView.addItemDecoration(itemDecoration);           
  • 其他api說明

7.1 使用過YCRefreshView庫的案例代碼

7.2 圖檔展示效果

7.3 部分案例圖展示[部分案例圖可以參考7.1]

  • v1.0 更新于2016年11月2日
  • v1.1 更新于2017年3月13日
  • v1.3 更新于2017年8月9日
  • v1.…… 更新于2018年1月5日
  • v2.2 更新于2018年1月17日
  • v2.3 更新于2018年2月9日
  • v2.4 更新于2018年3月19日
  • v2.5.6 更新于2018年8月6日
  • v2.5.7 更新于2019年3月3日
  • v2.5.8 更新于2019年3月4日

01.關于部落格彙總連結

02.關于我的部落格

03.勘誤及提問

  • 如果有疑問或者發現錯誤,可以在相應的 issues 進行提問或勘誤。如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。

04.關于LICENSE

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.           

關于開源庫位址:

繼續閱讀