Начиная новый проект я решила попробовать полностью отказаться от XML-файлов, на основе которых формируются layouts, а экраны создавать с помощью библиотеки Anko. Имея небольшой опыт разработки под Android (порядка 2-х лет) и еще меньший опыт написания кода на Kotlin (чуть больше полугода) я сразу столкнулась с проблемой включения в приложение Navigation Architecture Component, а вернее отрисовкой Bottom Navigation Bar, создаваемом BottomNavigationView.
Первым делом я обратилась к просторам Инета, чтобы найти возможное решение. Но все статьи найденные мной с той или иной степенью доходчивости рассказывали о том как работать с компонентами навигации и никто (из тех чьи статьи я проштудировала) не делал это на Anko. Решив поставленную задачу, я предлагаю сообществу свой вариант создания Bottom Navigation Bar.
Полностью весь код можно посмотреть здесь
Я пропускаю этап создания нового проекта в Android Studio, отмечу только что для работы с Anko и Navigation Architecture Component в build.gradle на уровне модуля необходимо добавить следующие зависимости:
implementation "org.jetbrains.anko:anko:$anko_version" implementation "org.jetbrains.anko:anko-constraint-layout:$anko_version" implementation "com.android.support.constraint:constraint-layout:2.0.0-alpha3" implementation 'android.arch.navigation:navigation-fragment:1.0.0-beta02' implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-beta02' implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-beta02' implementation 'com.google.android.material:material:1.0.0'
Следующий шаг — создаем структуру будущего приложения. Для отрисовки основной активити вместо xml-файла создаем класс MainActivityUI наследуемый от интерфейса AnkoComponent:
class MainActivityUI: AnkoComponent<MainActivity> { override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) { constraintLayout { } } }
В классе MainActivity setContentView(R.layout.activity_main) заменяем на MainActivityUI().setContentView(this).
Затем создаем package fragments в котором будут лежать наши фрагменты и package ui для размещения классов, отвечающих за отрисовку экранов соответствующих фрагментов. Так выглядит структура проекта:
fragments ui HomeUI UsersUI DetailsUI MoreUI HomeFragment UsersFragment DetailsFragment MoreFragment
Теперь займемся непосредственно навигацией и созданием Bottom Navigation Bar.
Подробное описание включения новых компонентов навигации и описание работы в Navigation Editor можно найти на странице документации здесь. Для создания файла (graph’а) навигации между экранами приложения, необходимо в папку res добавить еще одну папку, а именно navigation и уже в неё добавить файл navigation_graph.xml. Для этого проекта он будет таким:
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation_graph" app:startDestination="@id/homeFragment"> <fragment android:id="@+id/homeFragment" android:name="com.arsinde.ankobottomnavbar.fragments.HomeFragment" android:label="HomeFragment"> <action android:id="@+id/action_homeFragment_to_detailsFragment" app:destination="@id/detailsFragment"/> </fragment> <fragment android:id="@+id/detailsFragment" android:name="com.arsinde.ankobottomnavbar.fragments.DetailsFragment" android:label="DetailsFragment"> <action android:id="@+id/action_detailsFragment_to_usersFragment" app:destination="@id/usersFragment"/> </fragment> <fragment android:id="@+id/usersFragment" android:name="com.arsinde.ankobottomnavbar.fragments.UsersFragment" android:label="UsersFragment"> <action android:id="@+id/action_usersFragment_to_moreFragment" app:destination="@id/moreFragment"/> </fragment> <fragment android:id="@+id/moreFragment" android:name="com.arsinde.ankobottomnavbar.fragments.MoreFragment" android:label="MoreFragment"/> </navigation>
Для отображения самого Bar’а необходимо создать еще одну ресурсную папку, а именно menu. В ней располагается файл отвечающий за видимую часть bar’а. Вот так он выглядит в этом проекте:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@id/homeFragment" android:icon="@drawable/ic_home" android:title="@string/menu_title_home"/> <item android:id="@id/usersFragment" android:icon="@drawable/ic_users" android:title="@string/menu_title_users"/> <item android:id="@id/detailsFragment" android:icon="@drawable/ic_info" android:title="@string/menu_title_details"/> <item android:id="@id/moreFragment" android:icon="@drawable/ic_more" android:title="@string/menu_title_more"/> </menu>
Настало время соединить все имеющееся вместе и посмотреть как же это работает.
Добавим в MainActivityUI контейнер для фрагментов, а также определим контейнер для navigation bar.
constraintLayout { val fragmentContainer = frameLayout { id = R.id.fragment_container }.lparams { width = matchParent height = matchConstraint } val bottomNavigation = bottomNavigation { id = R.id.bottom_nav_view inflateMenu(R.menu.bottom_navigation_menu) } applyConstraintSet { fragmentContainer { connect( START to START of PARENT_ID, END to END of PARENT_ID, TOP to TOP of PARENT_ID, BOTTOM to TOP of R.id.bottom_nav_view ) } bottomNavigation { connect( START to START of PARENT_ID, END to END of PARENT_ID, TOP to BOTTOM of R.id.fragment_container, BOTTOM to BOTTOM of PARENT_ID ) } } }
Особо следует указать на то, что bottomNavigation в данном примере это extantion функция, имеющая следующий вид:
inline fun ViewManager.bottomNavigation(init: BottomNavigationView.() -> Unit = {}) = ankoView({ BottomNavigationView(it) }, theme = 0, init = init)
Теперь в MainActivity необходимо определить объект NavHostFragment (см. в доке):
private val host by lazy { NavHostFragment.create(R.navigation.navigation_graph) }
А в методе onCreate() определить:
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, host) .setPrimaryNavigationFragment(host) .commit()
Завершающий штрих — добавим в onStart() MainActivity объект класса NavController, который позволяет осуществлять переход между фрагментами выбирая тот или иной объект navigation bar’а:
override fun onStart() { super.onStart() val navController = host.findNavController() findViewById<BottomNavigationView>(R.id.bottom_nav_view)?.setupWithNavController(navController) navController.addOnDestinationChangedListener{_, destination, _ -> val dest: String = try { resources.getResourceName(destination.id) } catch (e: Resources.NotFoundException) { Integer.toString(destination.id) } Log.d("NavigationActivity", "Navigated to $dest") } }
Запускаем приложение и vois là…

