动态修改BottomNavigationBar的大小

本文主要探讨了在Android中如何动态修改系统自带BottomNavigationBar的高度问题。由于默认高度固定,当设置高度大于56dp时会出现视觉问题。通过分析源码发现,BottomNavigationBar加载时将R.layout.bottom_navigation_bar_container.xml布局添加到自身上,而该容器的高度被设定为56dp。解决方案是找到BottomNavigationBar的第一个子视图(mContainer),将其高度更改为MATCH_PARENT,从而实现自定义高度。附有参考代码供读者根据需求调整。

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();
            }
        }
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值