Android反射的简单应用之BottomNavigationView

本文探讨了在Android中如何利用反射技术优化BottomNavigationView的显示效果,特别是在选项超过3个时,通过反射实现与设计图一致的样式。文章介绍了反射的基本使用、何时使用以及注意事项,并提供了相关代码示例和源码链接。
摘要由CSDN通过智能技术生成

反射

反射(Reflection)在java和android开发过程中都非常有用,但是在android中的使用往往会影响app的性能,因此使用反射时要在适当的情况下使用。

什么时候使用呢?

反射耗时的多少与被反射类的大小有关系,它本质上是对类的成员列表进行遍历。如果这个类的成员越多,遍历的时间越长,整个反射的时间也就越多,如果类的成员较少,影响也是较小的。例如在以Activity作为测试对象,Activity是个大类,因此反射耗时比较正常。反射是把双刃剑,在适当的情况下使用非常利于程序的架构,如果使用不当会造成性能损耗。

如何使用呢?

我们不妨先看看这篇文章—公共技术点之 Java 反射 Reflection,相信你可以使用里面的方法来实现自己所需的功能。
这里就让我们来总结下反射的一些方法:

  • 获取class对象:
    正常来获取class时我们都是直接用 object.class 或者 通过该类的对象(Object o = new Object(),)来获取class( o.getclass()),下面则是通过class所在的包名来获取对象。
// 加载指定的 Class 对象,参数为要加载的类的完整路径
public static Class<?> forName (String className)

// 加载指定的 Class 对象,参数 1 为要加载的类的完整路径,例如"com.loften.sample.test";
// 参数 2 为是否要初始化该 Class 对象,参数 3 为指定加载该类的 ClassLoader.
public static Class<?> forName (String className, boolean shouldInitialize, ClassLoader classLoader)

//在调用 Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出 ClassNotFoundException
  • 获取构造函数:
// 获取一个公有的构造函数,参数为可变参数,如果构造函数有参数,那么需要将参数的类型传递给 getConstructor 方法
public Constructor<T> getConstructor (Class...<?> parameterTypes)
// 获取目标类所有的公有构造函数
public Constructor[]<?> getConstructors ()

注意,当你通过反射获取到 Constructor、Method、Field 后,在反射调用之前将此对象的 accessible 标志设置为 true,以此来提升反射速度。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

Constructor<?> constructor = clz.getConstructor(String.class);
// 设置 Constructor 的 Accessible
constructor.setAccessible(true);

// 设置 Methohd 的 Accessible
Method learnMethod = Student.class.getMethod("learn", String.class);
learnMethod.setAccessible(true);
  • 获取类中函数
// 获取 Class 对象中指定函数名和参数的函数,参数一为函数名,参数 2 为参数类型列表
public Method getDeclaredMethod (String name, Class...<?> parameterTypes)

// 获取该 Class 对象中的所有函数( 不包含从父类继承的函数 )
public Method[] getDeclaredMethods ()

// 获取指定的 Class 对象中的**公有**函数,参数一为函数名,参数 2 为参数类型列表
public Method getMethod (String name, Class...<?> parameterTypes)

// 获取该 Class 对象中的所有**公有**函数 ( 包含从父类和接口类集成下来的函数 )
public Method[] getMethods ()

//这里需要注意的是 getDeclaredMethodgetDeclaredMethods 包含 privateprotecteddefaultpublic 的函数,并且通过这两个函数获取到的只是在自身中定义的函数,从父类中集成的函数不能够获取到。而 getMethodgetMethods 只包含 public 函数,父类中的公有函数也能够获取到。
  • 获取类中属性:
// 获取 Class 对象中指定属性名的属性,参数一为属性名
public Method getDeclaredField (String name)

// 获取该 Class 对象中的所有属性( 不包含从父类继承的属性 )
public Method[] getDeclaredFields ()

// 获取指定的 Class 对象中的**公有**属性,参数一为属性名
public Method getField (String name)

// 获取该 Class 对象中的所有**公有**属性 ( 包含从父类和接口类集成下来的公有属性 )
public Method[] getFields ()

//这里需要注意的是 getDeclaredFieldgetDeclaredFields 包含 privateprotecteddefaultpublic 的属性,并且通过这两个函数获取到的只是在自身中定义的属性,从父类中集成的属性不能够获取到。而 getFieldgetFields 只包含 public 属性,父类中的公有属性也能够获取到。
  • 获取父类和接口:
/**
* 获取父类:只需 getSuperclass(),后续获取属性方法同上
*/
private static void getParentFields(){
    TestImp testImp = new TestImp("anni", 20, "45kg", "女", 180);
    Class<?> superClass = testImp.getClass().getSuperclass();
    while (superClass != null) {
        System.out.println("TestImp's super class is : " + superClass.getName());
        // 再获取父类的上一层父类,直到最后的 Object 类,Object 的父类为 null
        superClass = superClass.getSuperclass();
    }
}
/**
* 获取接口
*/
private static void showInterfaces() {
    TestImp testImp = new TestImp("anni", 20, "45kg", "女", 180);
    Class<?>[] interfaceses = testImp.getClass().getInterfaces();
    for (Class<?> class1 : interfaceses) {
        System.out.println("TestImp's interface is : " + class1.getName());
    }
}
  • 获取注解:
// 获取指定类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) ;
// 获取 Class 对象中的所有注解
public Annotation[] getAnnotations() ;
实际应用:

Material Design中的 BottomNavigationView 给出的样式跟我们正常使用的demo不一样,主要是我们的产品还未采用这样样式,不废话,先给图:

官方 修改后

上述第一幅是当选项大于3个时的样式(选项等于3个时则与第二幅图一直),往往与我们的设计图不一致,官方也暂未提供接口让我们去调用修改,因此我们可以通过反射,也就可以实现第二幅图的效果,自定义的BottomNavigationView代码如下:

public class DivBottomNavigationView extends BottomNavigationView {

    public DivBottomNavigationView(Context context) {
        super(context);
    }

    public DivBottomNavigationView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DivBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void SetNormalBottomNavigation() {

        BottomNavigationMenuView mMenuView = getField(getClass().getSuperclass(), this, "mMenuView", BottomNavigationMenuView.class);
        BottomNavigationItemView[] mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons", BottomNavigationItemView[].class);
        for (BottomNavigationItemView button : mButtons) {
            TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel", TextView.class);
            TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel", TextView.class);

            setField(button.getClass(), button, "mShiftAmount", 0);
            setField(button.getClass(), button, "mScaleUpFactor", 1);
            setField(button.getClass(), button, "mScaleDownFactor", 1);

            mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabel.getTextSize());

            setField(button.getClass(), button, "mShiftingMode", false);
        }
        setField(mMenuView.getClass(), mMenuView, "mShiftingMode", false);

        mMenuView.updateMenuView();
    }


    public int getCurrentItem() {

        BottomNavigationMenuView mMenuView = getField(getClass().getSuperclass(), this, "mMenuView", BottomNavigationMenuView.class);
        BottomNavigationItemView[] mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons", BottomNavigationItemView[].class);
        Menu menu = getMenu();
        for (int i = 0; i < mButtons.length; i++) {
            if (menu.getItem(i).isChecked()) {
                return i;
            }
        }
        return 0;
    }

    public void setCurrentItem(int item) {

        if (item < 0 || item >= getMaxItemCount()) {
            throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - "
                    + (getMaxItemCount() - 1) + ". Actually " + item);
        }

        BottomNavigationMenuView mMenuView = getField(getClass().getSuperclass(), this, "mMenuView", BottomNavigationMenuView.class);
        BottomNavigationItemView[] mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons", BottomNavigationItemView[].class);
        View.OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "mOnClickListener", View.OnClickListener.class);
        mOnClickListener.onClick(mButtons[item]);
    }

    /**
     * get private filed in this specific object
     *
     * @param targetClass
     * @param instance    the filed owner
     * @param fieldName
     * @param fieldType   the field type
     * @param <T>
     * @return field if success, null otherwise.
     */
    private <T> T getField(Class targetClass, Object instance, String fieldName, Class<T> fieldType) {
        try {
            Field field = targetClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return (T) field.get(instance);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * change the field value
     *
     * @param targetClass
     * @param instance      the filed owner
     * @param fieldName
     * @param value
     */
    private void setField(Class targetClass, Object instance, String fieldName, Object value) {
        try {
            Field field = targetClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(instance, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

这里附上我的源码地址:https://github.com/myloften/BottomNavigationViewSample

如果想要有更多自定义的BottomNavigationView的用法,可以参考下面链接:
https://github.com/ittianyu/BottomNavigationViewEx

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,`BottomNavigationView` 是一个用于底部导航栏的组件,通常用于展示不同功能选项。如果你想要去掉`BottomNavigationView` 的文字标签(标题),你可以通过自定义视图或者修改其默认样式来实现。以下是两种常见的方法: 1. **使用自定义`Menu`和`Item`:** - 创建一个不包含文字的`Menu`,然后在`setNavigationItemSelectedListener`中使用自定义`MenuItem`: ```java @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); // 根据id执行对应的逻辑,不需要文字显示,所以item.setTitle(null); return super.onNavigationItemSelected(item); } ``` 2. **自定义布局:** - 使用自定义的`BottomNavigationView` 的布局,如`FrameLayout` 替代默认的`BottomNavigationView` 并在内部添加图标或者其他视图: ```xml <FrameLayout android:id="@+id/bottom_navigation_view" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" /> // 在代码中添加你的图标或视图 View iconView = LayoutInflater.from(context).inflate(R.layout.bottom_navigation_item_icon, bottomNavigationView, false); iconView.setOnClickListener(yourOnClickListener); bottomNavigationView.addView(iconView); ``` 需要注意的是,如果你选择自定义布局,可能需要重写一些事件处理逻辑。 **相关问题--:** 1. 如何在Android中创建自定义菜单项? 2. 底部导航栏的`setNavigationItemSelectedListener` 方法怎么用? 3. 如果我选择自定义布局,如何确保点击响应正确?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值