Начиная новый проект я решила попробовать полностью отказаться от 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 la…
saag
Что-то кода кажется прям много, на мой непросвященный взгляд, тот же bottomNavigationBar во Flutter'e выглядит так:
bottomNavigationBar: new BottomNavigationBar(items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text(«Home»),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text(«Search»),
)
])
это bottomNavigationBar с двумя иконками, правда без обработчиков их нажатия.