整理了一下之前的项目,发现一个关于 Toolbar 的问题,Toolbar 中的 NavigationIcon 与 Title 的距离似乎有点过大:

网上查了一下发现是从 Support 包 V23.0.0 之后才出现这种情况,而我的这个项目使用的是 V28.0.0 的包,看来这个问题历史悠久啊。
打开 Toolbar 的源码:
public class Toolbar extends ViewGroup {
private RtlSpacingHelper mContentInsets; // 对应的属性名称为 contentInsetStart
private int mContentInsetStartWithNavigation; // 对应的属性名称为 contentInsetStartWithNavigation
...
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
final int paddingLeft = getPaddingLeft(); // 获取系统的偏移量
int left = paddingLeft;
...
// 计算 Navigation 的 Layout
if (shouldLayout(mNavButtonView)) {
if (isRtl) {
...
} else {
left = layoutChildLeft(mNavButtonView, left, collapsingMargins, alignmentHeight);
}
}
...
final int contentInsetLeft = getCurrentContentInsetLeft();
left = Math.max(left, contentInsetLeft);
...
if (layoutTitle || layoutSubtitle) {
...
if (isRtl) {
...
} else {
...
int titleLeft = left;
if (layoutTitle) {
...
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
}
...
}
}
...
}
...
private int layoutChildLeft(View child, int left, int[] collapsingMargins, int alignmentHeight) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int l = lp.leftMargin - collapsingMargins[0];
left += Math.max(0, l);
collapsingMargins[0] = Math.max(0, -l);
final int top = getChildTop(child, alignmentHeight);
final int childWidth = child.getMeasuredWidth();
child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
left += childWidth + lp.rightMargin;
return left;
}
/**
* Gets the content inset that will be used on the left side of the bar in the current toolbar configuration.
*
* @return the current content inset left in pixels
* @see #getContentInsetStartWithNavigation()
* @see #getContentInsetEndWithActions()
*/
public int getCurrentContentInsetLeft() {
return isLayoutRtl() ? getCurrentContentInsetEnd() : getCurrentContentInsetStart();
}
/**
* Gets the content inset that will be used on the starting side of the bar in the current toolbar configuration.
*
* @return the current content inset start in pixels
* @see #getContentInsetStartWithNavigation()
*/
public int getCurrentContentInsetStart() {
return getNavigationIcon() != null ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0)) : getContentInsetStart();
}
}
看 onLayout() 里面,isRtl 这个变量用于判断是否从右往左显示,我们的 Toolbar 是从左往右显示的,所以只需要看值为 false 的情况即可。
在计算 Navigation 的 Layout 中,进到 layoutChildLeft() 方法里进行计算,计算完之后 left 的距离为 paddingLeft + mNavButtonView 的宽度 + mNavButtonView 自身的偏移量。
接下来就进到 getCurrentContentInsetLeft() 方法,因为是从左向右显示所以会调用 getCurrentContentInsetStart() 这个方法,由于有 NavigationIcon,所以就会走前面的分支。
其中 Math.max(mContentInsetStartWithNavigation, 0) 返回的就是 mContentInsetStartWithNavigation 这个值,mContentInsetStartWithNavigation 这个值就是从 contentInsetStartWithNavigation 这个属性中取得的;getContentInsetStart() 这个方法的返回值就是 contentInsetStart 这个属性对应的值。
所以最后就是比较 contentInsetStart 和 contentInsetStartWithNavigation 这两个属性的值。
接下来我们来看这两个属性的值在修改前后的版本中到底是多少,具体的文件应找到对应版本的 appcompact-v7 包的「arr」文件,然后解压找到 /res/values/values.xml,当然也可以通过『Android Studio』的跳转查找功能。
在 V22.2.0 的版本中,描述 Toolbar 属性的内容如下:
<style name="Base.Widget.AppCompat.Toolbar" parent="android:Widget">
...
<item name="contentInsetStart">16dp</item>
</style>
可以发现 contentInsetStart 的值是 16dp,但没有 contentInsetStartWithNavigation 这个属性,这是因为 contentInsetStartWithNavigation 这个属性是在之后的版本才加上的,而之前的版本 Toolbar 代码中只会根据 contentInsetStart 来计算 Title 的左边距。
再来看看在 V28.0.0 版本中的代码:
<style name="Base.V7.Widget.AppCompat.Toolbar" parent="android:Widget">
...
<item name="contentInsetStart">16dp</item>
<item name="contentInsetStartWithNavigation">@dimen/abc_action_bar_content_inset_with_nav</item>
</style>
contentInsetStart 的值也还是 16dp,contentInsetStartWithNavigation 这个值定义在 <dimen> 中,我们去找找这个值:
<dimen name="abc_action_bar_content_inset_with_nav">72dp</dimen>
再回到一开始的那段代码:
final int contentInsetLeft = getCurrentContentInsetLeft(); // 核心的方法,返回就是那个让距离错误的值
left = Math.max(left, contentInsetLeft); // left 会从之前的 left 值也就是计算过 Navigation 的距离之后,和 contentInsetLeft 比较,取最大值
left 的值一开始是 NavigationIcon 的宽度,一般为 56dp,而 contentInsetLeft 这个值是 72dp,取最大值之后 left 的值就变成了 72dp,就最后导致了距离显示异常。
了解了问题产生的原因,就该说说解决方法了,解决方法也很简单,在 Toolbar 控件属性中添加一句即可:
<android.support.v7.widget.Toolbar
...
app:contentInsetStartWithNavigation="0dp" />
也可以通过指定 <style> 的方法:
<style name="NoSpaceActionBarTheme" parent="Base.Widget.AppCompat.Toolbar">
<item name="contentInsetStart">0dp</item>
<item name="contentInsetStartWithNavigation">0dp</item>
</style>
再在 Toolbar 控件属性中指定:
<android.support.v7.widget.Toolbar
...
style="@style/NoSpaceActionBarTheme" />
完美解决,看看实际效果图:

不过一时半会真的想不通为什么 Android 要加一个这样的东西…
