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

楊充發表於2019-04-07

目錄介紹

  • 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.其他內容介紹

開源庫地址:github.com/yangchong21…

  • 自定義支援上拉載入更多,下拉重新整理,可以自定義頭部和底部,可以新增多個header,使用一個原生recyclerView就可以搞定複雜介面。支援自由切換狀態【載入中,載入成功,載入失敗,沒網路等狀態】的控制元件,可以自定義狀態檢視View。擴充功能【支援長按拖拽,側滑刪除】,輕量級,可以選擇性新增 。持續更新……

1.複雜頁面庫介紹

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

2.本庫優勢亮點

2.1 支援多種狀態切換管理

  • 支援在佈局中或者程式碼設定自定義不同狀態的view,一行程式碼既可以切換不同的狀態,十分方便。
  • 針對自定義狀態view或者layout,可以實現互動功能,比如當切換到網路異常的頁面,可以點選頁面控制元件取重新整理資料或者跳轉設定網路頁面

2.2 支援新增多個header和footer

  • 支援新增多個自定義header頭部佈局,可以自定義footer底部佈局。十分方便實現複雜type的佈局頁面,結構上層次分明,便於維護。
  • 支援複雜介面使用,比如有的頁面包含有輪播圖,按鈕組合,橫向滑動list,還有複雜list,那麼用這個控制元件就可以搞定。

2.3 支援側滑功能和拖拽移動

  • 輕量級側滑刪除選單,直接巢狀item佈局即可使用,使用十分簡單
  • 通過自定義ItemTouchHelper實現RecyclerView條目Item拖拽排序,只是設定是否拖拽,設定拖拽item的背景顏色,優化了拖拽效果,比如在拖拽過程中設定item的縮放和漸變效果

2.4 其他亮點介紹

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

3.如何使用介紹

3.1 最基礎的使用

  • 首先在整合:
    • 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){
    
            }
        }
    }
    複製程式碼

3.2 新增下拉重新整理和載入更多監聽

  • 下拉重新整理監聽操作
    //設定重新整理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();
        }
    });
    複製程式碼

3.3 新增header和footer操作

  • 新增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。

3.4 設定資料和重新整理

  • 新增所有資料,可以是集合,也可以是陣列
    //新增所有資料
    adapter.addAll(data);
    //新增單挑資料
    adapter.add(data);
    複製程式碼
  • 插入,重新整理和刪除資料
    //插入指定索引資料,單個資料
    adapter.insert(data, pos);
    //插入指定索引資料,多個資料
    adapter.insertAll(data, pos);
    //重新整理指定索引資料
    adapter.update(data, pos);
    //刪除資料,指定資料
    adapter.remove(data);
    //刪除資料,指定索引
    adapter.remove(pos);
    //清空所有資料
    複製程式碼

3.5 設定adapter

  • 注意自定義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());
            }
        }
    }
    複製程式碼

3.6 設定條目點選事件

  • 條目單擊點選事件,長按事件[省略,可以自己看程式碼]
    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;
            }
        }
    });
    複製程式碼

3.7 設定側滑刪除功能[QQ側滑刪除]

  • 在佈局檔案中,這裡省略部分程式碼
    <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) {
    
        }
    });
    複製程式碼

3.8 輕量級拖拽排序與滑動刪除

  • 處理長按拖拽,滑動刪除的功能。輕量級,自由選擇是否實現。
    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);
    複製程式碼

4.關於狀態切換

4.1 關於佈局內容

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

4.2 關於實現思路

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

4.3 關於狀態切換api呼叫

  • 如下所示
    //設定載入資料完畢狀態
    recyclerView.showRecycler();
    //設定載入資料為空狀態
    recyclerView.showEmpty();
    //設定載入錯誤狀態
    recyclerView.showError();
    //設定載入資料中狀態
    recyclerView.showProgress();
    複製程式碼

4.4 關於自定義狀態佈局

  • 如下所示
    //設定空狀態頁面自定義佈局
    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);
    複製程式碼

4.5 關於自定義佈局互動事件處理

  • 有時候,載入頁面出現異常情況,比如沒有網路會顯示自定義的網路異常頁面。現在需要點選異常頁面按鈕等等操作,那麼該如何做呢?
    //注意需要
    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) {
            //比如,跳轉到網路設定頁面,或者再次重新整理資料,或者其他操作等等
        }
    });
    複製程式碼

5.常用api介紹

  • 狀態切換方法說明
    //設定載入資料完畢狀態
    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.實現效果展示

7.1 使用過YCRefreshView庫的案例程式碼

7.2 圖片展示效果

1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
6.jpg
7.jpg
8.jpg
9.jpg
10.jpg
11.jpg
12.jpg

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

image
image

8.版本更新說明

  • 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日

9.參考資料說明

10.其他內容介紹

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.
複製程式碼

關於開源庫地址:github.com/yangchong21…

相關文章