整理了一下之前的项目,发现一个关于 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 要加一个这样的东西…