從setContentView談談android的佈局層級

因爲android各版本的佈局層級會有所差異,所以先告訴大家我測試的環境背景,如有在別的系統版本下面測試的結果有所出入請在下面留言支出,方便更多的讀者可以從中獲益,謝謝大家!

android studio:2.2.2

java版本:1.8

系統版本:ubuntu 

sdk版本:minSdkVersion 19, targetSdkVersion 25

手機版本:樂視6.01


前言

當我們新建一個應用的時候如果選擇的是創建一個空的activity,那麼AS默認會給我們重寫onCreate()方法,並且在這個方法中爲我們添加上setContenView方法,我們常規的做法是從這裏進入對應的佈局文件中刪掉不必要的代碼然後開始我們的佈局代碼編寫。今天我們的講解就從註釋掉setContentView方法開始深入的講解一下activity中佈局的層級問題。

在這之前我先教大家如何使用AS查看應用的佈局,當手機鏈接上AS之後,我們在打印logcat的地方也就是Android monitor標籤這裏會看到這樣一個一行選項:



箭頭所示的圖標就是我們用來分析app佈局的利器:layout inspector,今天我們的佈局層級也是通過它來分析的。


一、不調用setContentView的情況下的佈局層級


首先我們新見一個帶有一個空白activity的app,取名LayoutHierarchy,下面的文章會簡稱爲LH,建好應用之後我們註釋掉setContentView這句代碼,然後運行到手機上面,然後點擊上面說的layoutInspector工具,過幾秒之後AS會打開類似下面這樣的界面:


這裏我標出來了整個窗口大致分爲這幾個部分,今天主要用到的上面的4個並排中的後面三個,他們分別是:用來查看層級的窗口,用來查看運行效果的窗口,用來詳細顯示選中層級中的某個佈局的詳細參數狀況。

將上面的3個窗口編號分別爲:1、2、3,我們首先來看下窗口一中有那些東西:



別看到張開後這麼複雜,其實我們摺疊好之後之後PhoneWindow$DecorView這一個,然後我們再次展開一層,我們發現DecorView有2個子佈局分別是LinearLayout和PhoneWindow$ImmersiveView,他們分別是我們的activity的根佈局和狀態欄的佈局。繼續往下展開,我們發現LinearLayout也有2個子佈局,分別是ViewStub和FrameLayout,其中前面一個和actionbar有關,後面一個和我們的佈局有關。再次展開FrameLayout,我們發現其只有一個子佈局:ActionBarOverlayLayout,因爲我們這個activity含有actionbar所以系統幫我們多套用了這層佈局。接着展開,我們發現這個佈局有2個子佈局,分別爲:ContentLayout和ActionBarContainer,前面一個是和我們SetContentView密切相關的一層佈局,我們的SetContentView裏面的佈局就是添加在這層佈局裏面的,因爲我們沒有調用setContentView方法,所以這裏沒有子佈局,後面的ActionBarContainer顧名思義就是和ActionBar相關的。我們展開ActionBarContainer發現其也有2個子佈局:ToolBar和ActionBarContextView,因爲我們的Activity不是直接繼承的Activity而是繼承了AppCompatActivity,所以這裏的ActionBar其實是ToolBar,這也就是我們爲什麼使用兼容的類的時候在Activity中獲取ActionBar不是直接調用的getActionBar()方法而是調用的getSupportActionBar()方法,之後我們教大家一種全中文圈沒幾個人使用的方式來獲取我們的actionbar,這裏的兩個子佈局ToolBar和ActionBarContextView,前面一個自然是我們的ActionBar,後面的一個呢就是在使用actionbar的風格爲splite分離模式的時候使用的。刨根問底,最後我們再次展開ToolBar,我們發現其也是2個子佈局:AppCompatTextView 和ActionMenuView,前面一個呢就是用來顯示我們的標題的也就是大家看到的那個顯示我們應用app的LayoutHierarchy的那個地方,後面的一個就是用來顯示actionbar的別的圖標的,比如home的返回按鈕等。

到這裏呢我就給大家逐層的分析了一遍佈局的層級,大家是不是很驚訝,我們這是新建了一個activity沒有添加我們自己的佈局的情況下已經有這麼多層的佈局了,要是加上我們自己的佈局,那一個頁面是需要渲染多少次纔可以繪製出來啊!而爲了性能優化,我們必須要減少佈局的層級,這樣我們的app才能更快的渲染出來,纔不會出現卡頓的現象。

另類的方式獲取actionBar

上面我們分析的時候知道其實在這裏actionbar使用的是toolbar,除了使用activity的getSupportACtionbar的方式獲取到之外,我們可以直接使用FindviewById方法來獲取actionbar,這裏就不得不使用我們前面3個窗口中的第三個窗口了。在第一個窗口中我們選中ToolBar,然後第三個窗口會顯示類似下面的這種情況:



這裏列出的各個屬性都是關於toolbar的,下面還有一些沒有截取出來,大家如果有興趣可以逐個的百度一下看看他們分別是用來控制toolbar的什麼效果的,看見我在圖中標出來的mId沒有,沒錯,這個就是toolbar在系統的id值。

來到代碼中,我們使用findviewbyId的方式來獲取到toolbar並且將他設置成紅色,修改後的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);

    }
}

運行後的效果如下:



我們看到整個狀態欄和標題欄都變成紅色的了,如果只是需要標題欄是紅色的,狀態欄不變色該怎麼辦呢?如果我們是21以上的手機,只需要設置調用方法getWindow().setStatusBarColor()方法就可以,添加後的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);
        getWindow().setStatusBarColor(Color.BLACK);
    }
}

好了,後了上面的代碼基礎之後,我們設置狀態欄和actionbar的顯示與隱藏,顏色值樣式值就變得非常的容易了,這裏就不帶大家一般般的去實現了,有不懂的歡迎在下面留言。

因爲網上流氓的網站太多了,經常有網站不經過我同樣就轉載我的文章,這裏貼上我的博客地址,好讓大家看見文章的時候知道作者是誰,我的博客地址:blog.csdn.net/qq379454816.

ok,下面就來教大家如何通過不設置setContentView就可以顯示我們的佈局,這裏我使用上面帶大家分析佈局層級的時候告訴大家的一個方法,就是上面說的那個FrameLayout,我們使用findviewById找到它,然後添加上我們的佈局文件,我們的佈局文件就是一個款和高都是marchParent的一個imageView,修改後代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);
        getWindow().setStatusBarColor(Color.BLACK);

        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        framelayout.addView(view, 0);

    }
}

運行上面的代碼就可以成功的加載上我們的佈局文件,效果圖如下:


看到這裏你有沒有一點小激動呢?是不是對以前的很多細節都恍然大悟呢?原來google給我們設計android系統的時候考慮到安全因素之外還是給我們提供了很多的途徑去實現新東西的,只是我們缺少發現的精神,網上的文章也是千遍一律,我們百度只是瞭解那些東西而不是用來照搬的,所以我們要多去實踐。

上面使用findviewbyId()方法也可以用在activity裏面是fragment的情況,我們直接可以把fragment放置到這個id爲content的裏面,這樣可以減少一層佈局。


全屏去掉actionbar的時候情況

下面我們來看看全屏沒有actionbar的情況下的佈局情況,我們在style文件中將主題設置爲:android:style/Theme.Holo.NoActionBar,然後設置屬性: <item name="android:windowFullscreen">true</item>,在activity中將前面獲取toolbar的代碼註釋掉,如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
//        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
//        toolbar.setBackgroundColor(Color.RED);
//        getWindow().setStatusBarColor(Color.BLACK);
//
        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        framelayout.addView(view, 0);



    }

運行後我們看下效果:


屏幕上面就一張圖片,我們打開layoutInspector來看看這個情況下的佈局:


可以看到就算是這樣的情況也會有3層佈局,這裏我們先不討論google爲什麼這樣設計,文章最後總結的時候我會告訴大家爲什麼要這樣設計。


二、使用setcontentView


我們先將代碼還原到創建app的初始狀態,如下所示:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
////        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
////        toolbar.setBackgroundColor(Color.RED);
////        getWindow().setStatusBarColor(Color.BLACK);
////
//        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
//        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
//        framelayout.addView(view, 0);



    }
}
看下運行效果:




LayoutInspector中的情況:



這種情況和我們使用findview替換的方式其實是一樣多的佈局,爲什麼會這樣呢?我們查看源碼發現,其實底層的setcontentView 的代碼也是find到content替換的:

 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

總結

今天給大家介紹這篇文章的目的是要大家認識到佈局是怎樣一層一層的繪製到屏幕上面的,只有我們清楚它的繪製流程我們纔可以在使用的過程中更加靈活的應對出現問題的時候也可以做到更加的從容。如果在使用fragment的情況下我們就可以直接用content來作爲fragment的容器而不需要再次設置一個容器,當然這隻能適合activity只有一個fragment的情況。那麼google爲什麼在一個簡單的activity上面設置那麼多層次的佈局呢?豈不是很浪費性能?其實google這樣設計也是有原因的,我們的視圖不只是用來展現一個畫面給用戶,我們更過的是讓手機與用戶去交互,交互的話就避免不了處理觸摸和點擊事件,那麼如何一層層的給點擊事件處理好呢?你當然想到了是否分發和攔截的情況,沒錯,google這樣設計就是爲了在系統層可以更好的控制點擊事件的分發。那麼這樣就可以只添加一個LinearLayout就可以了,爲什麼下面還添加一個FrameLayout?也許他這樣設計還處於考慮別的原因,我暫時不知道爲什麼這樣就設計,如果有知道的同學歡迎給我留言,感謝大家能看到這裏,碼字不容易,如果你覺得這篇文章對你有些許的幫助,請給個贊,謝謝大家!


掃描關注我的微信公衆號:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章