Android 讓你的布局飛起來

xiaoguo.gif

前言

在Android項(xiàng)目開發(fā)中一個(gè)界面的顯示狀態(tài)包括好幾種:內(nèi)容界面剑辫,loading界面干旧,網(wǎng)絡(luò)錯(cuò)誤界面等等;以前開發(fā)的時(shí)候都是直接把這些界面include到main界面中揭斧,然后動(dòng)態(tài)去切換界面,后來發(fā)現(xiàn)這樣處理不容易復(fù)用到其他項(xiàng)目中,而且在activity中處理這些狀態(tài)的顯示和隱藏比較亂讹开,所以就想著能不能封裝一個(gè)類來管理這些狀態(tài)View的切換盅视。

思路

為了讓View狀態(tài)的切換和Activity徹底分離開,必須把這些狀態(tài)View都封裝到一個(gè)管理類中旦万,然后暴露出幾個(gè)方法來實(shí)現(xiàn)View之間的切換闹击,因?yàn)樵诓煌捻?xiàng)目中可以需要的View也不一樣,所以考慮把管理類設(shè)計(jì)成builder模式來自由的添加需要的狀態(tài)View成艘。

實(shí)現(xiàn)

通常一個(gè)界面會(huì)包括:內(nèi)容赏半,空數(shù)據(jù),異常錯(cuò)誤淆两,加載断箫,網(wǎng)絡(luò)錯(cuò)誤等5種狀態(tài)的View,所以我們就設(shè)置這5種狀態(tài)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);   
      }
}

狀態(tài)管理類用到了建造者模式秋冰,上面是builder內(nèi)部類仲义,總共有11個(gè)屬性,loadingLayoutResId和contentLayoutResId代表等待加載和顯示內(nèi)容的xml文件剑勾;netWorkErrorVs埃撵,emptyDataVs,errorVs代表另外幾種異常狀態(tài)虽另,那為什么這幾種狀態(tài)要用ViewStub暂刘,因?yàn)樵诮缑鏍顟B(tài)切換中l(wèi)oading和內(nèi)容View都是一直需要加載顯示的,但是其他的3個(gè)只有在沒數(shù)據(jù)或者網(wǎng)絡(luò)異常的情況下才會(huì)加載顯示捂刺,所以用ViewStub來加載他們可以提高性能谣拣。

在錯(cuò)誤的幾個(gè)界面需要重試按鈕重新加載數(shù)據(jù),netWorkErrorRetryViewId, emptyDataRetryViewId, errorRetryViewId分別為幾個(gè)狀態(tài)界面重試按鈕的id叠萍, 如果這幾個(gè)按鈕的id是一樣的話就直接給retryViewId屬性賦值即可芝发,retryViewId優(yōu)先級最高。

onShowHideViewListener為狀態(tài)View顯示隱藏監(jiān)聽事件
onRetryListener為重試加載按鈕的監(jiān)聽事件

接下來需要把這些View添加到一個(gè)根View中返回給Activity苛谷,為了方便顯示隱藏這些View辅鲸,我們在根View中定義一個(gè)集合屬性,然后把這些View添加到集合當(dāng)中管理

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

這個(gè)集合Key為id腹殿,Value為View独悴,id為根View類內(nèi)部自定義的id,通過id找到對應(yīng)的View來顯示隱藏View锣尉,下面通過一個(gè)方法來看下它的切換邏輯

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

首先調(diào)用inflateLayout方法刻炒,方法返回true然后調(diào)用showHideViewById方法,
下面來看看inflateLayout方法的實(shí)現(xiàn)

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判斷來執(zhí)行不同的代碼自沧,首先判斷ViewStub是否為空坟奥,如果為空就代表沒有添加這個(gè)View就返回false树瞭,不為空就加載View并且添加到集合當(dāng)中,然后調(diào)用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访敌,如果顯示隱藏監(jiān)聽事件不為空凉敲,就分別調(diào)用它的顯示和隱藏方法,下面再來看看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 的優(yōu)先級最好寺旺,如果它不為0爷抓,就用它去查找View實(shí)例,然后如果View實(shí)例和重試監(jiān)聽都不為空就添加點(diǎn)擊事件阻塑,點(diǎn)擊事件里調(diào)用onRetryListener監(jiān)聽的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(); 顯示你的內(nèi)容view

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

  statusLayoutManager.showError(); 顯示error view

  statusLayoutManager.showNetWorkError();  顯示網(wǎng)絡(luò)異常view

結(jié)束語

至此,核心邏輯和代碼都已經(jīng)分析完成叮姑,想看如何調(diào)用和源碼的朋友可以移步至:https://github.com/chenpengfei88/StatusLayout

我還有一篇封裝底部導(dǎo)航欄的文章唉地,大家有興趣也可以看看
http://www.reibang.com/p/7cccb5c054da#

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市传透,隨后出現(xiàn)的幾起案子耘沼,更是在濱河造成了極大的恐慌,老刑警劉巖朱盐,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件群嗤,死亡現(xiàn)場離奇詭異,居然都是意外死亡兵琳,警方通過查閱死者的電腦和手機(jī)狂秘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躯肌,“玉大人者春,你說我怎么就攤上這事∏迮” “怎么了钱烟?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫡丙。 經(jīng)常有香客問我拴袭,道長,這世上最難降的妖魔是什么曙博? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任拥刻,我火速辦了婚禮,結(jié)果婚禮上父泳,老公的妹妹穿的比我還像新娘般哼。我一直安慰自己吴汪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布蒸眠。 她就那樣靜靜地躺著浇坐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黔宛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天擒贸,我揣著相機(jī)與錄音臀晃,去河邊找鬼。 笑死介劫,一個(gè)胖子當(dāng)著我的面吹牛徽惋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播座韵,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼险绘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了誉碴?” 一聲冷哼從身側(cè)響起宦棺,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黔帕,沒想到半個(gè)月后代咸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡成黄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年呐芥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奋岁。...
    茶點(diǎn)故事閱讀 38,629評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡思瘟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闻伶,到底是詐尸還是另有隱情滨攻,我是刑警寧澤,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布虾攻,位于F島的核電站铡买,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霎箍。R本人自食惡果不足惜奇钞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漂坏。 院中可真熱鬧景埃,春花似錦媒至、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至完慧,卻和暖如春谋旦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屈尼。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工册着, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脾歧。 一個(gè)月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓甲捏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鞭执。 傳聞我的和親對象是個(gè)殘疾皇子司顿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容