1. 程式人生 > >Toolbar的Title與NavigationIcon距離異常

Toolbar的Title與NavigationIcon距離異常

NavigationIcon和Title的距離正確

距離顯示正確.jpg

NavigationIcon和Title的距離出現了異常

距離顯示異常.jpg

問題的解決方法

解決辦法很簡單,見程式碼
為了方便起見,先定義一個Toolbar的Theme

<style name="NoSpaceActionBarTheme" parent="Base.Widget.AppCompat.Toolbar">
    <item name="contentInsetStart">0dp</item>
    <item name="contentInsetStartWithNavigation">0dp</item>
</style>

如果在佈局檔案中新增Toolbar的話可以通過增加style來實現,程式碼如下

<android.support.v7.widget.Toolbar
            style="@style/NoSpaceActionBarTheme"
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:navigationIcon="?attr/homeAsUpIndicator"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

如果定義過Activity的Theme是ActionBar的話,可以在Theme的定義中加上一句程式碼,如下

<style name="ActionBarTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="toolbarStyle">@style/NoSpaceActionBarTheme</item>
</style>

問題原因

為了搞明白為什麼Support包從V22.2.0版本升級到V24.0.0就會出現這樣的問題,還是需要翻看Toolbar的原始碼,OK,我們直接看Toolbar的程式碼(V24.0.0包中),順便說一下Toolbar是在appcompact-v7包下面

//用到的主要屬性名稱為contentInsetStart
private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
//對應的屬性名稱為contentInsetStartWithNavigation
private int mContentInsetStartWithNavigation;

mContentInsets 這個成員變數和mContentInsetStartWithNavigation用來控制NavigationIcon和Title之間的距離的,我們接著看這兩個變數是如何影響這個距離的

最主要的程式碼在Toolbar中的onLayout方法中,下面我摘取主要程式碼來說明

final int paddingLeft = getPaddingLeft();
//首先是獲取系統的偏移量    
int left = paddingLeft;
//這段程式碼用來計算Navigation的Layout
if (shouldLayout(mNavButtonView)) {
        if (isRtl) {
            right = layoutChildRight(mNavButtonView, right, collapsingMargins,
                    alignmentHeight);
        } else {
            //計算完之後left的距離為paddingLeft+mNavButtonView的寬度+mNavButtonView自身的偏移量
            left = layoutChildLeft(mNavButtonView, left, collapsingMargins,
                    alignmentHeight);
        }
    }
//核心的方法,返回就是那個讓距離錯誤的值
final int contentInsetLeft = getCurrentContentInsetLeft();
//left會從之前的left值也就是計算過Navigation的距離之後 和contentInsetLeft比較,取最大值
left = Math.max(left, contentInsetLeft);
...接下來計算Title的佈局的時候左邊距就是用的這個left

我們來看看getCurrentContentInsetLeft()這個方法

public int getCurrentContentInsetLeft() {
    return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
            ? getCurrentContentInsetEnd()
            : getCurrentContentInsetStart();
}

因為我們是從左向右顯示所以會呼叫getCurrentContentInsetStart()這個方法,我們繼續看這個方法

public int getCurrentContentInsetStart() {
    return getNavigationIcon() != null
            ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))
            : getContentInsetStart();
}

首先我們是有NavigationIcon的所以會走這個分支

Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))

其中Math.max(mContentInsetStartWithNavigation, 0)返回的就是mContentInsetStartWithNavigation這個值
mContentInsetStartWithNavigation這個值就是從contentInsetStartWithNavigation這個屬性中取得的
getContentInsetStart()這個返回的值就是contentInsetStart這個屬性對應的值

所以最後就是比較contentInsetStart和contentInsetStartWithNavigation這兩個屬性的值


OK,接下來我們來看這兩個屬性的值在V22.2.0和V24.0.0的版本中到底是多少
具體的檔案為首先找到對應版本的appcompact-v7包的aar檔案
然後解壓找到/res/values/values.xml這個檔案

首先說明預設Toolbar的Style是Widget.AppCompat.Toolbar
Widget.AppCompat.Toolbar的Parent是Base.Widget.AppCompat.Toolbar
所以只要找到Base.Widget.AppCompat.Toolbar對應的Style就OK了
首先我們來看V22.2.0版本中,我找到了描述Toolbar屬性的這段內容

<style name="Base.Widget.AppCompat.Toolbar" parent="android:Widget">
    <item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
    <item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
    <item name="android:minHeight">?attr/actionBarSize</item>
    <item name="titleMargins">4dp</item>
    <item name="maxButtonHeight">56dp</item>
    <item name="collapseIcon">?attr/homeAsUpIndicator</item>
    <item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
    <item name="contentInsetStart">16dp</item>
</style>

我們發現contentInsetStart這個是16dp,而沒有contentInsetStartWithNavigation這個屬性,這是因為contentInsetStartWithNavigation這個屬性是在V22之後的版本才加上的,而V22的Toolbar程式碼中只會根據contentInsetStart來計算Title的左邊距
接下來我們來看V24.0.0版本中的程式碼

<style name="Base.Widget.AppCompat.Toolbar" parent="android:Widget">
    <item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
    <item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
    <item name="android:minHeight">?attr/actionBarSize</item>
    <item name="titleMargin">4dp</item>
    <item name="maxButtonHeight">@dimen/abc_action_bar_default_height_material</item>
    <item name="buttonGravity">top</item>
    <item name="collapseIcon">?attr/homeAsUpIndicator</item>
    <item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
    <item name="contentInsetStart">16dp</item>
    <item name="contentInsetStartWithNavigation">@dimen/abc_action_bar_content_inset_with_nav</item>
    <item name="android:paddingLeft">@dimen/abc_action_bar_default_padding_start_material</item>
    <item name="android:paddingRight">@dimen/abc_action_bar_default_padding_end_material</item>
</style>

OK,contentInsetStart這個也是16dp,contentInsetStartWithNavigation這個定義在dimen中,我們來看看這個值

<dimen name="abc_action_bar_content_inset_with_nav">72dp</dimen>

OK,我們回過來看這段程式碼

//核心的方法,返回就是那個讓距離錯誤的值
final int contentInsetLeft = getCurrentContentInsetLeft();
//left會從之前的left值也就是計算過Navigation的距離之後 和contentInsetLeft比較,取最大值
left = Math.max(left, contentInsetLeft);

left的值一開始是NavigationIcon的寬度,一般為56dp,而contentInsetLeft這個值是72dp,所以left的值就變成了72dp,就最後導致了距離顯示異常

至此,我們終於瞭解了這個錯誤的來龍去脈,不僅瞭解了怎麼改,也瞭解了為什麼這麼改,同時瞭解了Toolbar的相關程式碼