因為android各版本的布局層級會有所差異,所以先告訴大家我測試的環(huán)境背景,如有在別的系統(tǒng)版本下面測試的結(jié)果有所出入請在下面留言支出,方便更多的讀者可以從中獲益,謝謝大家!
android studio:2.2.2
java版本:1.8
系統(tǒng)版本:ubuntu
sdk版本:minSdkVersion 19,?targetSdkVersion 25
手機版本:樂視6.01
前言
當(dāng)我們新建一個應(yīng)用的時候如果選擇的是創(chuàng)建一個空的activity,那么AS默認(rèn)會給我們重寫onCreate()方法,并且在這個方法中為我們添加上setContenView方法,我們常規(guī)的做法是從這里進(jìn)入對應(yīng)的布局文件中刪掉不必要的代碼然后開始我們的布局代碼編寫。今天我們的講解就從注釋掉setContentView方法開始深入的講解一下activity中布局的層級問題。
在這之前我先教大家如何使用AS查看應(yīng)用的布局,當(dāng)手機鏈接上AS之后,我們在打印logcat的地方也就是Android monitor標(biāo)簽這里會看到這樣一個一行選項:
箭頭所示的圖標(biāo)就是我們用來分析app布局的利器:layout inspector,今天我們的布局層級也是通過它來分析的。
一、不調(diào)用setContentView的情況下的布局層級
首先我們新見一個帶有一個空白activity的app,取名LayoutHierarchy,下面的文章會簡稱為LH,建好應(yīng)用之后我們注釋掉setContentView這句代碼,然后運行到手機上面,然后點擊上面說的layoutInspector工具,過幾秒之后AS會打開類似下面這樣的界面:
這里我標(biāo)出來了整個窗口大致分為這幾個部分,今天主要用到的上面的4個并排中的后面三個,他們分別是:用來查看層級的窗口,用來查看運行效果的窗口,用來詳細(xì)顯示選中層級中的某個布局的詳細(xì)參數(shù)狀況。
將上面的3個窗口編號分別為:1、2、3,我們首先來看下窗口一中有那些東西:
別看到張開后這么復(fù)雜,其實我們折疊好之后之后PhoneWindow$DecorView這一個,然后我們再次展開一層,我們發(fā)現(xiàn)DecorView有2個子布局分別是LinearLayout和PhoneWindow$ImmersiveView,他們分別是我們的activity的根布局和狀態(tài)欄的布局。繼續(xù)往下展開,我們發(fā)現(xiàn)LinearLayout也有2個子布局,分別是ViewStub和FrameLayout,其中前面一個和actionbar有關(guān),后面一個和我們的布局有關(guān)。再次展開FrameLayout,我們發(fā)現(xiàn)其只有一個子布局:ActionBarOverlayLayout,因為我們這個activity含有actionbar所以系統(tǒng)幫我們多套用了這層布局。接著展開,我們發(fā)現(xiàn)這個布局有2個子布局,分別為:ContentLayout和ActionBarContainer,前面一個是和我們SetContentView密切相關(guān)的一層布局,我們的SetContentView里面的布局就是添加在這層布局里面的,因為我們沒有調(diào)用setContentView方法,所以這里沒有子布局,后面的ActionBarContainer顧名思義就是和ActionBar相關(guān)的。我們展開ActionBarContainer發(fā)現(xiàn)其也有2個子布局:ToolBar和ActionBarContextView,因為我們的Activity不是直接繼承的Activity而是繼承了AppCompatActivity,所以這里的ActionBar其實是ToolBar,這也就是我們?yōu)槭裁词褂眉嫒莸念惖臅r候在Activity中獲取ActionBar不是直接調(diào)用的getActionBar()方法而是調(diào)用的getSupportActionBar()方法,之后我們教大家一種全中文圈沒幾個人使用的方式來獲取我們的actionbar,這里的兩個子布局ToolBar和ActionBarContextView,前面一個自然是我們的ActionBar,后面的一個呢就是在使用actionbar的風(fēng)格為splite分離模式的時候使用的。刨根問底,最后我們再次展開ToolBar,我們發(fā)現(xiàn)其也是2個子布局:AppCompatTextView 和ActionMenuView,前面一個呢就是用來顯示我們的標(biāo)題的也就是大家看到的那個顯示我們應(yīng)用app的LayoutHierarchy的那個地方,后面的一個就是用來顯示actionbar的別的圖標(biāo)的,比如home的返回按鈕等。
到這里呢我就給大家逐層的分析了一遍布局的層級,大家是不是很驚訝,我們這是新建了一個activity沒有添加我們自己的布局的情況下已經(jīng)有這么多層的布局了,要是加上我們自己的布局,那一個頁面是需要渲染多少次才可以繪制出來啊!而為了性能優(yōu)化,我們必須要減少布局的層級,這樣我們的app才能更快的渲染出來,才不會出現(xiàn)卡頓的現(xiàn)象。
另類的方式獲取actionBar
上面我們分析的時候知道其實在這里actionbar使用的是toolbar,除了使用activity的getSupportACtionbar的方式獲取到之外,我們可以直接使用FindviewById方法來獲取actionbar,這里就不得不使用我們前面3個窗口中的第三個窗口了。在第一個窗口中我們選中ToolBar,然后第三個窗口會顯示類似下面的這種情況:
這里列出的各個屬性都是關(guān)于toolbar的,下面還有一些沒有截取出來,大家如果有興趣可以逐個的百度一下看看他們分別是用來控制toolbar的什么效果的,看見我在圖中標(biāo)出來的mId沒有,沒錯,這個就是toolbar在系統(tǒng)的id值。
來到代碼中,我們使用findviewbyId的方式來獲取到toolbar并且將他設(shè)置成紅色,修改后的代碼如下:
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);
}
}
運行后的效果如下:
我們看到整個狀態(tài)欄和標(biāo)題欄都變成紅色的了,如果只是需要標(biāo)題欄是紅色的,狀態(tài)欄不變色該怎么辦呢?如果我們是21以上的手機,只需要設(shè)置調(diào)用方法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);
}
}
好了,后了上面的代碼基礎(chǔ)之后,我們設(shè)置狀態(tài)欄和actionbar的顯示與隱藏,顏色值樣式值就變得非常的容易了,這里就不帶大家一般般的去實現(xiàn)了,有不懂的歡迎在下面留言。
因為網(wǎng)上流氓的網(wǎng)站太多了,經(jīng)常有網(wǎng)站不經(jīng)過我同樣就轉(zhuǎn)載我的文章,這里貼上我的博客地址,好讓大家看見文章的時候知道作者是誰,我的博客地址:blog.csdn.net/qq379454816.
ok,下面就來教大家如何通過不設(shè)置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);
}
}
運行上面的代碼就可以成功的加載上我們的布局文件,效果圖如下:
看到這里你有沒有一點小激動呢?是不是對以前的很多細(xì)節(jié)都恍然大悟呢?原來google給我們設(shè)計android系統(tǒng)的時候考慮到安全因素之外還是給我們提供了很多的途徑去實現(xiàn)新東西的,只是我們?nèi)鄙侔l(fā)現(xiàn)的精神,網(wǎng)上的文章也是千遍一律,我們百度只是了解那些東西而不是用來照搬的,所以我們要多去實踐。
上面使用findviewbyId()方法也可以用在activity里面是fragment的情況,我們直接可以把fragment放置到這個id為content的里面,這樣可以減少一層布局。
全屏去掉actionbar的時候情況
下面我們來看看全屏沒有actionbar的情況下的布局情況,我們在style文件中將主題設(shè)置為:android:style/Theme.Holo.NoActionBar,然后設(shè)置屬性:?true,在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為什么這樣設(shè)計,文章最后總結(jié)的時候我會告訴大家為什么要這樣設(shè)計。
二、使用setcontentView
我們先將代碼還原到創(chuàng)建app的初始狀態(tài),如下所示:
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替換的方式其實是一樣多的布局,為什么會這樣呢?我們查看源碼發(fā)現(xiàn),其實底層的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();
}
總結(jié)
今天給大家介紹這篇文章的目的是要大家認(rèn)識到布局是怎樣一層一層的繪制到屏幕上面的,只有我們清楚它的繪制流程我們才可以在使用的過程中更加靈活的應(yīng)對出現(xiàn)問題的時候也可以做到更加的從容。如果在使用fragment的情況下我們就可以直接用content來作為fragment的容器而不需要再次設(shè)置一個容器,當(dāng)然這只能適合activity只有一個fragment的情況。那么google為什么在一個簡單的activity上面設(shè)置那么多層次的布局呢?豈不是很浪費性能?其實google這樣設(shè)計也是有原因的,我們的視圖不只是用來展現(xiàn)一個畫面給用戶,我們更過的是讓手機與用戶去交互,交互的話就避免不了處理觸摸和點擊事件,那么如何一層層的給點擊事件處理好呢?你當(dāng)然想到了是否分發(fā)和攔截的情況,沒錯,google這樣設(shè)計就是為了在系統(tǒng)層可以更好的控制點擊事件的分發(fā)。那么這樣就可以只添加一個LinearLayout就可以了,為什么下面還添加一個FrameLayout?也許他這樣設(shè)計還處于考慮別的原因,我暫時不知道為什么這樣就設(shè)計,如果有知道的同學(xué)歡迎給我留言,感謝大家能看到這里,碼字不容易,如果你覺得這篇文章對你有些許的幫助,請給個贊,謝謝大家!
歡迎關(guān)注我的微信公眾號“AndroidBook”,也可以掃描下面的二維碼來關(guān)注: