android系统自带的BottomNavigationBar用起来是很方便,就是固定了大小,即使在xml里面配置了高度。当你设置的高度大于56dp,你会发现底部有两层,后面一层白底的布局比BottomNavigationBar高,露出来了。在看了BottomNavigationBar源码后发现在加载它的布局的时候,把inflate出来的view添加到BottomNavigationBar上了。
private void init() {
// MarginLayoutParams marginParams = new ViewGroup.MarginLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getContext().getResources().getDimension(R.dimen.bottom_navigation_padded_height)));
// marginParams.setMargins(0, (int) getContext().getResources().getDimension(R.dimen.bottom_navigation_top_margin_correction), 0, 0);
setLayoutParams(new ViewGroup.LayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)));
LayoutInflater inflater = LayoutInflater.from(getContext());
//这里把this作为root,并且设置了attachToRoot为true
View parentView = inflater.inflate(R.layout.bottom_navigation_bar_container, this, true);
mBackgroundOverlay = parentView.findViewById(R.id.bottom_navigation_bar_overLay);
//这个就是加载出来的布局文件,是一个FrameLayout
mContainer = parentView.findViewById(R.id.bottom_navigation_bar_container);
//这里是放用户添加的的导航item,是一个LinearLayout
mTabContainer = parentView.findViewById(R.id.bottom_navigation_bar_item_container);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.setOutlineProvider(ViewOutlineProvider.BOUNDS);
} else {
//to do
}
ViewCompat.setElevation(this, mElevation);
setClipToPadding(false);
}
我们知道,如果attachToRoot了,返回的就是root布局,这里也告诉我们在inflate列表类View的item时候不能调用
inflater.inflate($layoutId$,root);因为该方法会调用inflater.inflate($layoutId$,root,root != null);即inflater.inflate($layoutId$,root,true);
会把root作为结果返回。这自然不是你想要的。要么调用inflater.inflate($layoutId$,null);非要带上root的时候,调用inflater.inflate($layoutId$,root,false);
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//1.把root作为result
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//2.这里是加载出来的布局
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//3.如果root不为空并且attachToRoot为true,则会把加载出来的布局add
//到root布局里
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
//4.如果root为空或者attachToRoot为false,就会把加载出来的布局赋值给result
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//5.返回result
return result;
}
}
看了上面我们知道,系统会加载 R.layout.bottom_navigation_bar_container.xml布局,并且添加到BottomNavigationBar的布局上。
<?xml version="1.0" encoding="utf-8"?>
<!--<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="@dimen/bottom_navigation_padded_height">-->
//这是mContainer的view
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_navigation_bar_container"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_navigation_height"
android:layout_gravity="bottom">
//这里是mBackgroundOverlay的view
<FrameLayout
android:id="@+id/bottom_navigation_bar_overLay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//这里是mTabContainer的view
<LinearLayout
android:id="@+id/bottom_navigation_bar_item_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal" />
</FrameLayout>
<!--</FrameLayout>-->
而mContainer的高度设置为:android:layout_height="@dimen/bottom_navigation_height",是56dp。到这里就很清楚了为啥无法修改BottomNavigationBar的高度了。所以find了BottomNavigationBar之后,getChildAt(0),就拿到了mContainer的view,重新设置它的高为MATCH_PARENT就可以了。
ViewGroup.LayoutParams lp0 = bottomNavigationBar.getChildAt(0).getLayoutParams();
lp0.height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomNavigationBar.getChildAt(0).setLayoutParams(lp0);
附上整个BottomNavigationBar布局的图片。
这是参考代码,大家根据自己需要修改相关控件。主要是搞清楚各个view对应哪个控件
public static void setIconItemMargin(BottomNavigationBar bottomNavigationBar, int space, int imgLen, int textSize) {
Class barClass = bottomNavigationBar.getClass();
//由于BottomNavigationBar在inflate创建的时候,attachToRoot = true;
//View parentView = inflater.inflate(R.layout.bottom_navigation_bar_container, this, true);
// 所以在加载它的布局文件(bottom_navigation_bar_container.xml)时(是一个FrameLayout),
// 会被add到我们自己在布局文件里面定义的BottomNavigationBar,所以此处getChildAt(0)就能拿到该布局
ViewGroup.LayoutParams lp0 = bottomNavigationBar.getChildAt(0).getLayoutParams();
lp0.height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomNavigationBar.getChildAt(0).setLayoutParams(lp0);
Field[] fields = barClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
try {
if (field.getName().equals("mTabContainer")) {
//反射得到 mTabContainer
LinearLayout mTabContainer = (LinearLayout) field.get(bottomNavigationBar);
for (int j = 0; j < mTabContainer.getChildCount(); j++) {
//获取到容器内的各个Tab
View view = mTabContainer.getChildAt(j);
//获取到Tab内的各个显示控件
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
FrameLayout container = (FrameLayout) view.findViewById(R.id.fixed_bottom_navigation_container);
container.setLayoutParams(params);
container.setPadding(dip2px(12), dip2px(0), dip2px(12), dip2px(0));
//获取到Tab内的文字控件
TextView labelView = (TextView) view.findViewById(com.ashokvarma.bottomnavigation.R.id.fixed_bottom_navigation_title);
//计算文字的高度DP值并设置,setTextSize为设置文字正方形的对角线长度,所以:文字高度(总内容高度减去间距和图片高度)*根号2即为对角线长度,此处用DP值,设置该值即可。
labelView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
labelView.setIncludeFontPadding(false);
labelView.setPadding(0, 0, 0, dip2px(space));
//获取到Tab内的图像控件
ImageView iconView = (ImageView) view.findViewById(com.ashokvarma.bottomnavigation.R.id.fixed_bottom_navigation_icon);
View iconContainer = view.findViewById(com.ashokvarma.bottomnavigation.R.id.fixed_bottom_navigation_icon_container);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(dip2px(imgLen * 1.6f), dip2px(imgLen * 1.5f));
lp.gravity = Gravity.CENTER_HORIZONTAL;
iconContainer.setLayoutParams(lp);
//设置图片参数,其中,MethodUtils.dip2px():换算dp值
params = new FrameLayout.LayoutParams(dip2px(imgLen), dip2px(imgLen));
params.setMargins(0, 0, 0, space / 2);
params.gravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM;
iconView.setLayoutParams(params);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}