Android 讓你的布局飛起來

xiaoguo.gif

前言

在Android項目開發中一個界面的顯示狀態包括好幾種:內容界面,loading界面,網絡錯誤界面等等;以前開發的時候都是直接把這些界面include到main界面中,然后動態去切換界面,后來發現這樣處理不容易復用到其他項目中,而且在activity中處理這些狀態的顯示和隱藏比較亂,所以就想著能不能封裝一個類來管理這些狀態View的切換。

思路

為了讓View狀態的切換和Activity徹底分離開,必須把這些狀態View都封裝到一個管理類中,然后暴露出幾個方法來實現View之間的切換,因為在不同的項目中可以需要的View也不一樣,所以考慮把管理類設計成builder模式來自由的添加需要的狀態View。

實現

通常一個界面會包括:內容,空數據,異常錯誤,加載,網絡錯誤等5種狀態的View,所以我們就設置這5種狀態View的切換

public static final class Builder {    

        private Context context;    
        private int loadingLayoutResId;    
        private int contentLayoutResId;    
        private ViewStub netWorkErrorVs;    
        private int netWorkErrorRetryViewId;
        private ViewStub emptyDataVs;
        private int emptyDataRetryViewId;
        private ViewStub errorVs;
        private int errorRetryViewId;
        private int retryViewId;
        private OnShowHideViewListener onShowHideViewListener;   
        private OnRetryListener onRetryListener; 

        public Builder(Context context) {       
            this.context = context;    
        }    

        public Builder loadingView(@LayoutRes int loadingLayoutResId) {    
            this.loadingLayoutResId = loadingLayoutResId;        
            return this;    
        }    

        public Builder netWorkErrorView(@LayoutRes int newWorkErrorId) {    
            netWorkErrorVs = new ViewStub(context);     
            netWorkErrorVs.setLayoutResource(newWorkErrorId);        
            return this;    
        }    

       public Builder emptyDataView(@LayoutRes int noDataViewId) {    
            emptyDataVs = new ViewStub(context);        
            emptyDataVs.setLayoutResource(noDataViewId);       
            return this;   
       }    

       public Builder errorView(@LayoutRes int errorViewId) {        
            errorVs = new ViewStub(context);   
            errorVs.setLayoutResource(errorViewId);        
            return this;    
       }    

      public Builder contentView(@LayoutRes int contentLayoutResId) {       
            this.contentLayoutResId = contentLayoutResId;        
            return this;    
      }    

        public Builder netWorkErrorRetryViewId(int netWorkErrorRetryViewId) {
            this.netWorkErrorRetryViewId = netWorkErrorRetryViewId;
            return this;
        }

        public Builder emptyDataRetryViewId(int emptyDataRetryViewId) {
            this.emptyDataRetryViewId = emptyDataRetryViewId;
            return this;
        }

        public Builder errorRetryViewId(int errorRetryViewId) {
            this.errorRetryViewId = errorRetryViewId;
            return this;
        }

        public Builder retryViewId(int retryViewId) {
            this.retryViewId = retryViewId;
            return this;
        }

      public Builder onShowHideViewListener(OnShowHideViewListener onShowHideViewListener) {       
             this.onShowHideViewListener = onShowHideViewListener;        
             return this;    
      }

       public Builder onRetryListener(OnRetryListener onRetryListener) {
            this.onRetryListener = onRetryListener;
            return this;
        }    

      public StatusLayoutManager build() {        
             return new StatusLayoutManager(this);   
      }
}

狀態管理類用到了建造者模式,上面是builder內部類,總共有11個屬性,loadingLayoutResId和contentLayoutResId代表等待加載和顯示內容的xml文件;netWorkErrorVs,emptyDataVs,errorVs代表另外幾種異常狀態,那為什么這幾種狀態要用ViewStub,因為在界面狀態切換中loading和內容View都是一直需要加載顯示的,但是其他的3個只有在沒數據或者網絡異常的情況下才會加載顯示,所以用ViewStub來加載他們可以提高性能。

在錯誤的幾個界面需要重試按鈕重新加載數據,netWorkErrorRetryViewId, emptyDataRetryViewId, errorRetryViewId分別為幾個狀態界面重試按鈕的id, 如果這幾個按鈕的id是一樣的話就直接給retryViewId屬性賦值即可,retryViewId優先級最高。

onShowHideViewListener為狀態View顯示隱藏監聽事件
onRetryListener為重試加載按鈕的監聽事件

接下來需要把這些View添加到一個根View中返回給Activity,為了方便顯示隱藏這些View,我們在根View中定義一個集合屬性,然后把這些View添加到集合當中管理

/** *  存放布局集合 */
private SparseArray<View> layoutSparseArray = new SparseArray();

這個集合Key為id,Value為View,id為根View類內部自定義的id,通過id找到對應的View來顯示隱藏View,下面通過一個方法來看下它的切換邏輯

/** *  顯示空數據 */
public void showEmptyData() {    
     if(inflateLayout(LAYOUT_EMPTYDATA_ID))      
      showHideViewById(LAYOUT_EMPTYDATA_ID);
}

首先調用inflateLayout方法,方法返回true然后調用showHideViewById方法,
下面來看看inflateLayout方法的實現

private boolean inflateLayout(int id) {    
    boolean isShow = true;    
    if(layoutSparseArray.get(id) != null) return isShow;    
    switch (id) {        
       case LAYOUT_NETWORK_ERROR_ID:            
         if(mStatusLayoutManager.netWorkErrorVs != null) {    
           View view = mStatusLayoutManager.netWorkErrorVs.inflate();
           retryLoad(view, mStatusLayoutManager.netWorkErrorRetryViewId);
           layoutSparseArray.put(id, view);                
           isShow = true;            
         } else {                
           isShow = false;            
         }            
         break;        

       case LAYOUT_ERROR_ID:            
           if(mStatusLayoutManager.errorVs != null) {
              View view = mStatusLayoutManager.errorVs.inflate();
              retryLoad(view, mStatusLayoutManager.errorRetryViewId);   
              layoutSparseArray.put(id, view);                
              isShow = true;            
           } else {                
              isShow = false;           
           }            
           break;        

      case LAYOUT_EMPTYDATA_ID:            
          if(mStatusLayoutManager.emptyDataVs != null) {
              View view = mStatusLayoutManager.emptyDataVs.inflate();
              retryLoad(view, mStatusLayoutManager.emptyDataRetryViewId);     
              layoutSparseArray.put(id, view);                
              isShow = true;            
          } else {                
              isShow = false;            
          }            
          break;    
      }    
      return isShow;
}

方法里面通過id判斷來執行不同的代碼,首先判斷ViewStub是否為空,如果為空就代表沒有添加這個View就返回false,不為空就加載View并且添加到集合當中,然后調用showHideViewById方法顯示隱藏View,retryLoad方法是給重試按鈕添加事件,先來看看showHideViewById方法邏輯

private void showHideViewById(int id) {    
    for(int i = 0; i < layoutSparseArray.size(); i++) {        
        int key = layoutSparseArray.keyAt(i);        
        View valueView = layoutSparseArray.valueAt(i);        
        //顯示該view        
        if(key == id) {            
            valueView.setVisibility(View.VISIBLE);            
            if(mStatusLayoutManager.onShowHideViewListener != null) 
               mStatusLayoutManager.onShowHideViewListener.onShowView(valueView, key);
         } else {           
             if(valueView.getVisibility() != View.GONE) {     
                 valueView.setVisibility(View.GONE);                
                 if(mStatusLayoutManager.onShowHideViewListener != null) 
                    mStatusLayoutManager.onShowHideViewListener.onHideView(valueView, key);
             } 
         }        
   }    
}

通過id找到需要顯示的View并且顯示它,隱藏其他View,如果顯示隱藏監聽事件不為空,就分別調用它的顯示和隱藏方法,下面再來看看retryLoad方法

 public void retryLoad(View view, int id) {
        View retryView = view.findViewById(mStatusLayoutManager.retryViewId != 0 ? mStatusLayoutManager.retryViewId : id);
        if(retryView == null || mStatusLayoutManager.onRetryListener == null) return;
        retryView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mStatusLayoutManager.onRetryListener.onRetry();
            }
        });
  }

可以看出retryViewId 的優先級最好,如果它不為0,就用它去查找View實例,然后如果View實例和重試監聽都不為空就添加點擊事件,點擊事件里調用onRetryListener監聽的onRetry方法。

使用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initToolBar();

        LinearLayout mainLinearLayout = (LinearLayout) findViewById(R.id.main_rl);
        statusLayoutManager = StatusLayoutManager.newBuilder(this)
                .contentView(R.layout.activity_content)
                .emptyDataView(R.layout.activity_emptydata)
                .errorView(R.layout.activity_error)
                .loadingView(R.layout.activity_loading)
                .netWorkErrorView(R.layout.activity_networkerror)
                .retryViewId(R.id.button_try)
                .onShowHideViewListener(new OnShowHideViewListener() {
                    @Override
                    public void onShowView(View view, int id) {
                    }

                    @Override
                    public void onHideView(View view, int id) {
                    }
                }).onRetryListener(new OnRetryListener() {
                    @Override
                    public void onRetry() {
                        statusLayoutManager.showLoading();

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }

                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        statusLayoutManager.showContent();
                                    }
                                });
                            }
                        }).start();

                    }
                }).build();

        mainLinearLayout.addView(statusLayoutManager.getRootLayout(), 1);

        statusLayoutManager.showLoading();
    }

StatusLayoutManager提供了一系列的方法來顯示不同布局View之間的切換

  statusLayoutManager.showLoading(); 顯示loading加載view

  statusLayoutManager.showContent(); 顯示你的內容view

  statusLayoutManager.showEmptyData(); 顯示空數據view

  statusLayoutManager.showError(); 顯示error view

  statusLayoutManager.showNetWorkError();  顯示網絡異常view

結束語

至此,核心邏輯和代碼都已經分析完成,想看如何調用和源碼的朋友可以移步至:https://github.com/chenpengfei88/StatusLayout

我還有一篇封裝底部導航欄的文章,大家有興趣也可以看看
http://www.lxweimin.com/p/7cccb5c054da#

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容