Android架构组件-Navigation的使用(一)
Android架构组件-Navigation的使用(二)
这篇主要介绍navigation和BottomNavigationView的使用,可以实现底部导航栏。
如果你的viewpager+tab是非滚动不左右滑动切换页面,可以更换了。

新建activity,选择Bottom Navigation Activity:

Bottom Navigation activity

如果新建activity没有该选项的话,请升级Android studio
代码自动产生activity,xml,menu,drawable。创建4个fragment,此处代码忽略,然后创建navigation,如何创建参考使用一
nav_bottom_navagation.xml代码如下:

                        

接着我们修改activity的XML,将自动产生的TextView替换为fragment

                        

fragment的地方需要注意:

  • 添加:android:name="androidx.navigation.fragment.NavHostFragment",如果自定义了NavHostFragment,就替换为自定义的路径+NavHostFragment
  • 配置 app:defaultNavHost 和 app:navGraph属性:app:defaultNavHost="true"
    app:navGraph="@navigation/nav_bottom_navagation"

需要注意:navigation里fragment的id需要和menu里item的id对应

下面就是修改activity:

override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_bottom_navigation)        var navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(R.id.frag_nav_bottom_navigation) as NavHostFragment        var navController: NavController = navHostFragment.navController        NavigationUI.setupWithNavController(navigation, navController)//        bottom_navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)     }//需要重写onSupportNavigateUp方法override fun onSupportNavigateUp(): Boolean {        return Navigation.findNavController(this, R.id.frag_nav_bottom_navigation).navigateUp()    }

需要将该自动产生的代码里的listener去掉,因为setupWithNavController里set了,可以看下面的源码

public static void setupWithNavController(            @NonNull final BottomNavigationView bottomNavigationView,            @NonNull final NavController navController) {        bottomNavigationView.setOnNavigationItemSelectedListener(                new BottomNavigationView.OnNavigationItemSelectedListener() {                    @Override                    public boolean onNavigationItemSelected(@NonNull MenuItem item) {                        return onNavDestinationSelected(item, navController, true);                    }                });        navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {            @Override            public void onNavigated(@NonNull NavController controller,                    @NonNull NavDestination destination) {                Menu menu = bottomNavigationView.getMenu();                for (int h = 0, size = menu.size(); h < size; h++) {                    MenuItem item = menu.getItem(h);                    if (matchDestination(destination, item.getItemId())) {                        item.setChecked(true);                    }                }            }        });    }

运行试试看,一个简单的类似tabview+viewpager的效果。

点击BottomNavigationView的时候,发现当menu的数量>3的时候,出现了动画的效果,跟设计完全不符,查看源码会发现:

* support:design:27.1.1版本的:

BottomNavigationMenuView里有个Boolean变量mShiftingMode,他的值是根据mShiftingMode = mMenu.size() > 3;控制的,让人苦恼的他是private,且menu的布局在onMeasure时是根据他的值设置的,当他为false的时候,没有那个动画效果。那就想办法设置他为false吧

@SuppressLint("RestrictedApi")fun BottomNavigationView.disableShiftMode() {    val menuView = this.getChildAt(0) as BottomNavigationMenuView    try {        val shiftingMode = menuView.javaClass.getDeclaredField("mShiftingMode")        shiftingMode.isAccessible = true        shiftingMode.setBoolean(menuView, false)        shiftingMode.isAccessible = false        for (i in 0 until menuView.childCount) {            val item = menuView.getChildAt(i) as BottomNavigationItemView            item.setShiftingMode(false)            // set once again checked value, so view will be updated            item.setChecked(item.itemData.isChecked)        }    } catch (e: NoSuchFieldException) {        Timber.e("Unable to get shift mode field: $e")    } catch (e: IllegalAccessException) {        Timber.e("Unable to change value of shift mode: $e")    }}

需要代码设置

bottomNavigationView.disableShiftMode()

运行看看,是不是4个menu均匀的分布了,这是27.1.1下的源码。

注意:27和28版本下navigation的源码是不一样的
如果你设置了4个menu均匀分布后,还会发现,他们带有选中变大的动画,这是因为,源码其实是2个view,他们有各自的大小值控制动画,附上源码:

public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        final Resources res = getResources();        int inactiveLabelSize =                res.getDimensionPixelSize(R.dimen.design_bottom_navigation_text_size);        int activeLabelSize = res.getDimensionPixelSize(                R.dimen.design_bottom_navigation_active_text_size);        mDefaultMargin = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_margin);        mShiftAmount = inactiveLabelSize - activeLabelSize;        mScaleUpFactor = 1f * activeLabelSize / inactiveLabelSize;        mScaleDownFactor = 1f * inactiveLabelSize / activeLabelSize;        LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true);        setBackgroundResource(R.drawable.design_bottom_navigation_item_background);        mIcon = findViewById(R.id.icon);        mSmallLabel = findViewById(R.id.smallLabel);        mLargeLabel = findViewById(R.id.largeLabel);    }

inactiveLabelSize和activeLabelSize,
如果不想要这样的动画的话,我们只需要将这2个值设为一样就可以了。在自己项目里添加:

14dp14dp
* support:design:28版本的:

可能是意识到BottomNavigationView的问题,他新添加了方法setLabelVisibilityMode,可以设置不同的LabelVisibilityMode,看源码有4种

@Retention(RetentionPolicy.SOURCE)public @interface LabelVisibilityMode {    int LABEL_VISIBILITY_AUTO = -1;    int LABEL_VISIBILITY_SELECTED = 0;    int LABEL_VISIBILITY_LABELED = 1;    int LABEL_VISIBILITY_UNLABELED = 2;}

可以在代码里设置:

navigation.labelVisibilityMode = LabelVisibilityMode.LABEL_VISIBILITY_SELECTED 

也可以在xml里设置:

app:labelVisibilityMode="labeled"

每种效果不一样,具体的你可以每个运行看看。

有的同学的menu图片使用了PNG,也是可以的,但需要图片四周是透明的,不然会是整块都是颜色值。
如果不想使用他的特性,就想使用自己彩色的图片,可以代码设置:

bottomNavigationView.itemIconTintList = null

运行的过程中发现,每次点击切换menu的时候,fragment都会重新oncreate。

如何避免fragment被回收重新创建呢?
还有印象我前面提到的自定义NavHostFragment吗?

查看源码发现NavHostFragment在create的时候,会在NavigatorProvider里添加创建的FragmentNavigator,而在FragmentNavigator里的navigate方法里对fragment做了处理

@Override    public void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        final Context context = requireContext();        mNavController = new NavController(context);        mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());       ...    }

我们自定义个FragmentNavigator,重写NavHostFragment的createFragmentNavigator

@Navigator.Name("tab_fragment")  // Use as custom tag at navigation.xmlclass TabNavigator (        private val context: Context,        private val manager: FragmentManager,        private val containerId: Int) : FragmentNavigator(context, manager, containerId) {    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {        val tag = destination.id.toString()        val transaction = manager.beginTransaction()        val currentFragment = manager.primaryNavigationFragment        if (currentFragment != null) {            transaction.hide(currentFragment)        }        var fragment = manager.findFragmentByTag(tag)        if (fragment == null) {            fragment = destination.createFragment(args)            transaction.add(containerId, fragment, tag)        } else {            transaction.show(fragment)        }        transaction.setPrimaryNavigationFragment(fragment)        transaction.setReorderingAllowed(true)        transaction.commit()        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)    }}class TabNavHostFragment: NavHostFragment() {    override fun createFragmentNavigator(): Navigator {        return TabNavigator(requireContext(), childFragmentManager, id)    }}

TabNavigator我们重命名了@Navigator.Name("tab_fragment"),在
navigation里我们需要更改fragment为tab_fragment

<?xml version="1.0" encoding="utf-8"?>            ...

同时我们需要将xml里的fragment修改:

activity里在获取NavHostFragment也需要修改:

override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_bottom_navigation)        //使用自定义的TabNavHostFragment,优化fragment每次重新create问题        var navHostFragment: TabNavHostFragment = supportFragmentManager.findFragmentById(R.id.frag_nav_bottom_navigation) as TabNavHostFragment        var navController: NavController = navHostFragment.navController        NavigationUI.setupWithNavController(navigation, navController)

在每个fragment的oncreate方法添加log,运行查看是不是在点击menu的时候,不会每次都执行oncreate了,而且只有在点击menu的时候才会创建fragment,实现了类似懒加载的方式。

如果你想动态的手动添加menu和fragment,不妨往下继续看。

在xml里将fragment去除app:navGraph的配置,
BottomNavigationView去除app:menu的配置

                        

代码手动的添加:

class BtNavTransactionActivity : AppCompatActivity() {    var homeFragment: HomeFragment? = null    var notificationsFragment: NotificationsFragment? = null    var dashboardFragment: DashboardFragment? = null    var testFragment: DashboardFragment? = null    var currentFragment: Fragment? = null    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->        when (item.itemId) {            0 -> {                if (homeFragment == null) {                    homeFragment = HomeFragment()                }                settingFragment(homeFragment!!)            }            1 -> {                if (notificationsFragment == null) {                    notificationsFragment = NotificationsFragment()                }                settingFragment(notificationsFragment!!)            }            2 -> {                if (dashboardFragment == null) {                    dashboardFragment = DashboardFragment()                }                settingFragment(dashboardFragment!!)            }            3 -> {                if (testFragment == null) {                    testFragment = DashboardFragment()                }                settingFragment(testFragment!!)            }        }        true    }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_bt_nav_transaction)        bottomNavigationView.menu.add(0, 0, 0, resources.getString(R.string.title_home)).setIcon(R.drawable.ic_home_black_24dp)        bottomNavigationView.menu.add(0, 1, 1, resources.getString(R.string.title_dashboard)).setIcon(R.drawable.ic_dashboard_black_24dp)        bottomNavigationView.menu.add(0, 2, 2, resources.getString(R.string.title_notifications)).setIcon(R.drawable.ic_notifications_black_24dp)        bottomNavigationView.menu.add(0, 3, 3, resources.getString(R.string.title_test)).setIcon(R.drawable.ic_notifications_black_24dp)        if (homeFragment == null) {            homeFragment = HomeFragment()        }        fragmentAdd(R.id.fragment, homeFragment!!)        currentFragment = homeFragment//        bottomNavigationView.itemIconTintList = null //如果使用的是自己的彩色png图片,需要设置        bottomNavigationView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)    }    /**      *用FragmentTransaction控制fragment的show和hide      *避免每次的oncreate      **/    private fun settingFragment(fragment: Fragment) {        if (currentFragment != fragment) {            val transaction = supportFragmentManager.beginTransaction()            if(!fragment.isAdded){                transaction.hide(currentFragment!!).add(R.id.fragment, fragment).commit()            } else {                transaction.hide(currentFragment!!).show(fragment).commit()            }            currentFragment = fragment        }    }    fun fragmentAdd(viewId: Int, fragment: Fragment) {        supportFragmentManager                .beginTransaction()                .add(viewId, fragment)                .commitAllowingStateLoss()    }}

OK,算是这几天navigation的使用总结吧。

补充注意:
按照上面步骤写代码的话,会出现fragment重叠的bug,可以重写onSaveInstanceState处理,这里就不介绍相关fragment重叠问题了。

更多相关文章

  1. 【Android】Android(安卓)Studio使用gradle导出jar包并混淆代码
  2. Android开发(十一)——ImageView的尺寸设置scaleType
  3. android源代码在线查看
  4. Android通知Notification的使用
  5. Android(安卓)自定义对话框去除白色边框代码
  6. Android(安卓)无法查看外部依赖jar的源码的问题
  7. Android里面编写退出主程序的提示代码
  8. Android学习之Pair类 (android.util.Pair )
  9. Android(安卓)与github的使用

随机推荐

  1. android中关于class Build以及如何修改an
  2. Android之Handler用法总结
  3. Android日志收集
  4. 开机动画(闪动的ANDROID字样的动画图片)
  5. textview中加链接
  6. Android中同时选择日期和时间
  7. [Android] AsyncTask使用实例---加载网络
  8. Android(安卓)TextView中文字通过Spannab
  9. Android的SQLite----重新认识Android(10)
  10. Android之TextView