Как бы реализовал NavGraph на FragmentManager
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация навигации через FragmentManager
Для реализации аналога NavGraph без использования Navigation Component, я бы создал собственный менеджер навигации, основанный на FragmentManager. Это потребует проектирования системы, которая управляет стеком фрагментов, обработкой транзакций и состоянием.
Ключевые компоненты системы
- Менеджер навигации — центральный класс, управляющий стеком и транзакциями.
- Точки входа — специальные контейнеры (например,
FrameLayout). - Конфигурация графа — определение экранов и переходов между ними.
Базовая реализация менеджера навигации
class CustomNavController(private val containerId: Int, private val fragmentManager: FragmentManager) {
private val backStack = ArrayDeque<String>() // Стек идентификаторов фрагментов
fun navigateTo(fragmentTag: String, fragment: Fragment, addToBackStack: Boolean = true) {
val transaction = fragmentManager.beginTransaction()
// Анимации перехода (опционально)
transaction.setCustomAnimations(
R.anim.slide_in_right,
R.anim.slide_out_left,
R.anim.slide_in_left,
R.anim.slide_out_right
)
// Скрываем текущий фрагмент, если он есть
fragmentManager.findFragmentById(containerId)?.let {
transaction.hide(it)
}
// Проверяем, не добавлен ли уже этот фрагмент
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag)
if (existingFragment != null) {
transaction.show(existingFragment)
} else {
transaction.add(containerId, fragment, fragmentTag)
}
if (addToBackStack) {
transaction.addToBackStack(fragmentTag)
backStack.addLast(fragmentTag)
}
transaction.commit()
}
fun navigateBack(): Boolean {
return if (backStack.isNotEmpty()) {
backStack.removeLast()
fragmentManager.popBackStack()
true
} else {
false
}
}
fun getCurrentFragment(): Fragment? {
return fragmentManager.findFragmentById(containerId)
}
}
Конфигурация навигационного графа
Для определения структуры приложения я бы использовал статическую конфигурацию или DSL подход:
object NavGraph {
private val destinations = mutableMapOf<String, () -> Fragment>()
fun registerDestination(tag: String, factory: () -> Fragment) {
destinations[tag] = factory
}
fun createFragment(tag: String): Fragment {
return destinations[tag]?.invoke()
?: throw IllegalArgumentException("Destination $tag not registered")
}
fun initGraph() {
registerDestination("home") { HomeFragment() }
registerDestination("profile") { ProfileFragment() }
registerDestination("settings") { SettingsFragment() }
registerDestination("details") { DetailsFragment() }
}
}
Интеграция с Activity
class MainActivity : AppCompatActivity() {
private lateinit var navController: CustomNavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Инициализация графа
NavGraph.initGraph()
// Создание навигационного контроллера
navController = CustomNavController(R.id.fragment_container, supportFragmentManager)
// Установка начального фрагмента
if (savedInstanceState == null) {
navController.navigateTo("home", NavGraph.createFragment("home"), false)
}
}
override fun onBackPressed() {
if (!navController.navigateBack()) {
super.onBackPressed()
}
}
// Методы для навигации из фрагментов
fun navigateTo(destination: String) {
navController.navigateTo(destination, NavGraph.createFragment(destination))
}
}
Особенности реализации
Управление жизненным циклом
Важно правильно обрабатывать состояние фрагментов при поворотах экрана и восстановлении процесса. Для этого нужно сохранять и восстанавливать стек навигации:
private const val BACK_STACK_KEY = "back_stack_state"
fun saveState(outState: Bundle) {
outState.putStringArray(BACK_STACK_KEY, backStack.toTypedArray())
}
fun restoreState(savedState: Bundle) {
val savedStack = savedState.getStringArray(BACK_STACK_KEY)
savedStack?.forEach { tag ->
backStack.addLast(tag)
}
}
Глубокие ссылки
Для поддержки deep links потребуется парсинг URI и маппинг на destination:
fun handleDeepLink(uri: Uri) {
when (uri.path) {
"/profile" -> navigateTo("profile")
"/settings" -> navigateTo("settings")
else -> navigateTo("home")
}
}
Анимации переходов
Можно реализовать кастомные анимации для разных типов переходов:
enum class TransitionType {
SLIDE, FADE, NONE
}
fun navigateWithTransition(fragmentTag: String, fragment: Fragment, transition: TransitionType) {
val transaction = fragmentManager.beginTransaction()
when (transition) {
TransitionType.SLIDE -> transaction.setCustomAnimations(...)
TransitionType.FADE -> transaction.setCustomAnimations(...)
TransitionType.NONE -> { /* без анимации */ }
}
// Остальная логика навигации...
}
Преимущества кастомной реализации
- Полный контроль над поведением навигации
- Гибкость в реализации специфичных сценариев
- Минимальные зависимости от сторонних библиотек
- Производительность — меньше накладных расходов
Недостатки подхода
- Больше boilerplate кода
- Ответственность за обработку edge cases (повороты экрана, восстановление процесса)
- Отсутствие готовых инструментов (NavHostFragment, безопасные аргументы)
Такой подход оправдан, когда требуется максимальная кастомизация или нужно избегать зависимостей от AndroidX Navigation. Однако для большинства проектов рекомендую использовать стандартный Navigation Component, который уже решает множество проблем из коробки.