Начиная новый проект я решила попробовать полностью отказаться от 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…


Скриншот

Комментарии (1)


  1. saag
    25.02.2019 16:14

    Что-то кода кажется прям много, на мой непросвященный взгляд, тот же 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 с двумя иконками, правда без обработчиков их нажатия.