問題描述
一般我們?cè)O(shè)置透明狀態(tài)欄的時(shí)候都是通過下面代碼進(jìn)行設(shè)置
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
然后在使用Toolbar的時(shí)候設(shè)置fitsSystemWindows="true"就可以成功設(shè)置透明狀態(tài)欄了,這里還通過設(shè)置android:minHeight和maxButtonHeight將默認(rèn)的Toolbar的高度56dp改為48dp饭弓。
<style name="toolbarStyle" parent="Base.Widget.AppCompat.Toolbar" >
<item name="android:background">@color/colorPrimary</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<item name="android:fitsSystemWindows">true</item>
<item name="theme">@style/ToolbarTheme</item>
<item name="popupTheme">@style/ToolbarPopupTheme</item>
<item name="titleTextAppearance">@style/ToolbarTitle</item>
<item name="android:minHeight">48dp</item>
<item name="maxButtonHeight">48dp</item>
</style>
然而這種設(shè)置方法在有EditText的布局中就會(huì)出現(xiàn)問題,假如有以下帶有EditText的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/toolbarStyle">
</android.support.v7.widget.Toolbar>
<EditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="#cccccc"
android:hint="我是EditText"
android:textColor="@android:color/black"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_below="@id/toolbar"
android:layout_above="@id/edittext"
android:text=""/>
</RelativeLayout>
沒有彈出鍵盤的情況下阀趴,這個(gè)布局的效果如下圖所示。

當(dāng)Activity設(shè)置android:windowSoftInputMode="adjustPan"時(shí)叔汁,則會(huì)出現(xiàn)Toolbar被移出屏幕的情況攻柠。

當(dāng)Activity設(shè)置android:windowSoftInputMode="adjustResize"時(shí),則會(huì)出現(xiàn)EditText被鍵盤覆蓋微驶、Toolbar被拉伸的情況

問題分析
fitsSystemWindow屬性
根據(jù)官方對(duì)fitsSystemWindows屬性(鏈接)的描述因苹,當(dāng)View的fitsSystemWindows設(shè)置為true的時(shí)候,系統(tǒng)會(huì)自動(dòng)為該View設(shè)置相應(yīng)的padding以適應(yīng)鍵盤款筑、狀態(tài)欄、導(dǎo)航欄等系統(tǒng)窗口杈湾,這就可以解釋為什么給Toolbar設(shè)置fitsSystemWindows之后Toolbar會(huì)自動(dòng)加上paddingTop以適應(yīng)狀態(tài)欄漆撞,如果沒有加上fitsSystemWindows=true浮驳,Toolbar則會(huì)有部分被狀態(tài)欄覆蓋至会。
Called by the view hierarchy when the content insets for a window have changed, to allow it to adjust its content to fit within those windows. The content insets tell you the space that the status bar, input method, and other system windows infringe on the application's window.
You do not normally need to deal with this function, since the default window decoration given to applications takes care of applying it to the content of the window. If you use SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION this will not be the case, and your content can be placed under those system elements. You can then use this method within your view hierarchy if you have parts of your UI which you would like to ensure are not being covered.
關(guān)于fitsSystemWindows的源碼實(shí)現(xiàn)可以看這篇文章(鏈接)
EditText問題分析
當(dāng)設(shè)置android:windowSoftInputMode="adjustPan"時(shí)奋献,根據(jù)官方對(duì)adjustPan(鏈接)的描述,設(shè)置這個(gè)屬性之后 Activity 主窗口的尺寸不會(huì)調(diào)整宣吱,而是會(huì)自動(dòng)平移窗口的內(nèi)容使EditText永遠(yuǎn)不會(huì)被鍵盤覆蓋杭攻,這就是為什么Toolbar會(huì)被移出屏幕的原因兆解。
“adjustPan”
不調(diào)整 Activity 主窗口的尺寸來為軟鍵盤騰出空間锅睛, 而是自動(dòng)平移窗口的內(nèi)容现拒,使當(dāng)前焦點(diǎn)永遠(yuǎn)不被鍵盤遮蓋印蔬,讓用戶始終都能看到其輸入的內(nèi)容例驹。 這通常不如尺寸調(diào)正可取眠饮,因?yàn)橛脩艨赡苄枰P(guān)閉軟鍵盤以到達(dá)被遮蓋的窗口部分或與這些部分進(jìn)行交互仪召。
當(dāng)設(shè)置設(shè)置android:windowSoftInputMode="adjustResize"時(shí),根據(jù)官方的解釋召娜,此時(shí)Activity窗口的尺寸會(huì)調(diào)整而為屏幕上的軟鍵盤騰出空間玖瘸,由于Toolbar設(shè)置了fitsSystemWindows為true且Toolbar的高度設(shè)置為wrap_content雅倒,因此Toolbar會(huì)被設(shè)置了一定的paddingBottom造成被拉伸蔑匣。
“adjustResize”
始終調(diào)整 Activity 主窗口的尺寸來為屏幕上的軟鍵盤騰出空間棕诵。
問題解決
首先不能在Toolbar設(shè)置fitsSystemWindows="true"讓系統(tǒng)自動(dòng)給Toolbar設(shè)置paddingTop校套,我們可以自己手動(dòng)給Toolbar添加狀態(tài)欄高度的paddingTop侨把,具體代碼如下。
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.toolbar);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar.setTitle("測(cè)試");
//設(shè)置透明狀態(tài)欄
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
toolbar.setPadding(0, getStatusBarHeight(this), 0, 0); //給Toolbar設(shè)置paddingTop
}
}
//通過反射獲取狀態(tài)欄高度,默認(rèn)25dp
private static int getStatusBarHeight(Context context) {
int statusBarHeight = dip2px(context, 25);
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusBarHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
return statusBarHeight;
}
//根據(jù)手機(jī)的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素)
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
首先是android:windowSoftInputMode="adjustPan"蜘拉,其實(shí)我們不應(yīng)該這一個(gè)布局文件設(shè)置為adjustPan,如果確實(shí)需要解決這個(gè)問題持寄,那么應(yīng)該給Toolbar下面的所有View用ScrollView給包括進(jìn)去稍味,這樣自動(dòng)平移只會(huì)移動(dòng)ScrollView里面的內(nèi)容。
對(duì)于android:windowSoftInputMode="adjustResize"掂碱,由于現(xiàn)在我們沒有對(duì)Toolbar設(shè)置fitsSystemWindows="true"慎冤,Toolbar沒有被拉伸疼燥,但是EditText卻被鍵盤覆蓋住,解決這個(gè)問題最好的方法就是給最外層的View設(shè)置fitsSystemWindows="true"粪薛,此時(shí)雖然EditText在鍵盤上面沒有被覆蓋住悴了,但是最外層的View由于設(shè)置了fitsSystemWindows="true"從而導(dǎo)致系統(tǒng)會(huì)給最外層的View設(shè)置paddingTop,導(dǎo)致的效果如下违寿。

解決這個(gè)問題的方法就是讓最外層的View不去添加系統(tǒng)給的padding湃交,通過重寫View的兩個(gè)方法就可以實(shí)現(xiàn),這兩個(gè)方法中fitSystemWindows在5.0以后就不支持了藤巢,因此5.0以后需要重寫onApplyWindowInsets來進(jìn)行適配搞莺,代碼如下温圆。
@Override
protected boolean fitSystemWindows(Rect insets) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
insets.left = 0;
insets.top = 0;
insets.right = 0;
}
return super.fitSystemWindows(insets);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom()));
} else {
return insets;
}
}
最終的效果圖如下熔掺,順利達(dá)到我們想要的效果券坞。

代碼
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.sparrow.toolbar.SoftInputRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/toolbarStyle">
</android.support.v7.widget.Toolbar>
<EditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="#cccccc"
android:hint="我是EditText"
android:textColor="@android:color/black"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="16sp"
android:layout_below="@id/toolbar"
android:layout_above="@id/edittext"
android:text="The default implementation works well for a situation where it is used with a container that covers the entire window, allowing it to apply the appropriate insets to its content on all edges. If you need a more complicated layout (such as two different views fitting system windows, one on the top of the window, and one on the bottom), you can override the method and handle the insets however you would like. Note that the insets provided by the framework are always relative to the far edges of the window, not accounting for the location of the called view within that window. (In fact when this method is called you do not yet know where the layout will place the view, as it is done before layout happens.)The default implementation works well for a situation where it is used with a container that covers the entire window, allowing it to apply the appropriate insets to its content on all edges. If you need a more complicated layout (such as two different views fitting system windows, one on the top of the window, and one on the bottom), you can override the method and handle the insets however you would like. Note that the insets provided by the framework are always relative to the far edges of the window, not accounting for the location of the called view within that window. (In fact when this method is called you do not yet know where the layout will place the view, as it is done before layout happens.)"
android:textColor="@android:color/black"/>
</com.sparrow.toolbar.SoftInputRelativeLayout>
SoftInputRelativeLayout.java
public class SoftInputRelativeLayout extends RelativeLayout{
public SoftInputRelativeLayout(Context context) {
super(context);
}
public SoftInputRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SoftInputRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SoftInputRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected boolean fitSystemWindows(Rect insets) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
insets.left = 0;
insets.top = 0;
insets.right = 0;
}
return super.fitSystemWindows(insets);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom()));
} else {
return insets;
}
}
}
MainActivity.java
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.toolbar);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar.setTitle("測(cè)試");
//設(shè)置透明狀態(tài)欄
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
toolbar.setPadding(0, getStatusBarHeight(this), 0, 0);
}
}
//獲取狀態(tài)欄高度
private static int getStatusBarHeight(Context context) {
int statusBarHeight = dip2px(context, 25);
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusBarHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
return statusBarHeight;
}
//根據(jù)手機(jī)的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素)
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <!-- 標(biāo)題欄顏色 -->
<item name="colorPrimaryDark">@color/colorAccent</item> <!-- 狀態(tài)欄顏色 -->
<item name="colorAccent">@color/colorAccent</item> <!-- 控件顏色 -->
<item name="android:textColorSecondary">#ffffff</item> <!-- 返回按鈕和三點(diǎn)更多按鈕顏色 -->
<item name="android:textColorPrimary">@android:color/white</item> <!--標(biāo)題文字顏色-->
</style>
<!-- toolbar標(biāo)題樣式 -->
<style name="ToolbarTitle" parent="@style/TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">16sp</item>
</style>
<style name="myPopupMenu" parent="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"></style>
<style name="MyPopupMenu" parent="Base.Widget.AppCompat.PopupMenu">
</style>
<!-- toolbar菜單文字顏色 -->
<style name="ToolbarTheme" parent="@style/ThemeOverlay.AppCompat.ActionBar">
<item name="actionMenuTextColor">@android:color/white</item> <!-- 菜單文字顏色 -->
<item name="actionMenuTextAppearance">@style/ToolbarMenuTextSize</item>
</style>
<!-- toolbar菜單文字尺寸 -->
<style name="ToolbarMenuTextSize" parent="@style/TextAppearance.AppCompat.Menu">
<item name="android:textSize">10sp</item>
</style>
<!-- toolbar彈出菜單樣式 -->
<style name="ToolbarPopupTheme" parent="@style/ThemeOverlay.AppCompat">
<item name="android:colorBackground">#212121</item> <!-- 彈窗菜單背景顏色 -->
<item name="actionOverflowMenuStyle">@style/OverflowMenuStyle</item>
</style>
<style name="OverflowMenuStyle" parent="Widget.AppCompat.Light.PopupMenu.Overflow">
<item name="overlapAnchor">false</item> <!--把該屬性改為false即可使menu位置位于toolbar之下-->
</style>
<style name="toolbarStyle" parent="Base.Widget.AppCompat.Toolbar" >
<item name="android:background">@color/colorPrimary</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<!--<item name="android:fitsSystemWindows">true</item>-->
<item name="theme">@style/ToolbarTheme</item>
<item name="popupTheme">@style/ToolbarPopupTheme</item>
<item name="titleTextAppearance">@style/ToolbarTitle</item>
<item name="android:minHeight">48dp</item>
<item name="maxButtonHeight">48dp</item>
</style>
</resources>
新的問題
在使用的過程中發(fā)現(xiàn)有用戶反饋太颤,說只要進(jìn)入我們采用該布局的頁面就會(huì)崩潰,我們查看了崩潰日志歌亲,發(fā)現(xiàn)有部分手機(jī)都使用了相同的一個(gè)安卓系統(tǒng)悍缠,并且版本都是19溅漾,android4.4.x暮胧,YunOS系統(tǒng)炼绘,異常信息為 java.lang.ClassNotFoundException: Didn't find class "android.view.WindowInsets" 脚曾。
這應(yīng)該是該系統(tǒng)的虛擬機(jī)加載類的方法不一樣,在加載一個(gè)類的時(shí)候也將函數(shù)涉及到的其他類也一起進(jìn)行加載了撞芍,因?yàn)閃indowInsets是Api為20才添加的,所有才會(huì)出現(xiàn)ClassNotFoundException這一個(gè)異常隘竭,解決方法網(wǎng)上有人給出方法,就是增加layout_v20文件夾仇轻,針對(duì)不同的版本寫不一樣的布局蹄殃,分別為api 20以上與20以下提供不同的布局懒棉,這是采用系統(tǒng)的限定符實(shí)現(xiàn)的妻导,之后20以上的原樣采用上述的方式醇疼,20以下去掉onApplyWindowInsets復(fù)寫卵蛉,這樣不同的版本加載不同的代碼就OK了缭受。
具體可以查看一下這篇博客(鏈接)蒲祈。