How to show the fragment's enterTransition above the exitTransition - android

I want to show the enterTransition on top of the exitTransition.
But as you can see in the gif, the enterTransition is below the exitTransition.
How can I solve this problem?
The navigation component is used for fragment transitions.
EnterFragment.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = Slide().apply {
duration = 2000
slideEdge = Gravity.END
}
}
ExitFragment.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = Slide().apply {
duration= 3000
slideEdge = Gravity.START
}
}
nav_main.xml
<action
android:id="#+id/move"
app:destination="#id/fragment_enter"
/>

Please refer to the official documentation for transition animations with navigation component: https://developer.android.com/guide/navigation/navigation-animate-transitions
You will need to put your animations in a respective animation xml file and then reference it in your nav_graph xml within the respective action. You can then specify when which animation should be used.

Related

Handling different startDestination in android single activity Navigation graph

I want to implement a single activity project.
After showing splash i want to navigate to HomeFragment or LoginFragment based on user's login condition. In the MainActivity :
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (userLoggedIn()) {
navigateToHomeScreen()
} else {
navigateToLoginScreen()
}
}
The question is how should i set the navigation xml? Which one of the fragments should be the startDestionation. What's the best approach for a smooth splash/login( sign-in/sign-up/forget-pass)/ home flow.

Jetpack compose deeplink handling branch.io

I'm using branch.io for handling deep links. Deep links can contain custom metadata in a form of JsonObject. The data can be obtained by setting up a listener, inside MainActivity#onStart() which is triggered when a link is clicked.
override fun onStart() {
super.onStart()
Branch
.sessionBuilder(this)
.withCallback { referringParams, error ->
if (error == null) {
val eventId = referringParams?.getString("id")
//Here I would like to navigate user to event screen
} else {
Timber.e(error.message)
}
}
.withData(this.intent?.data).init()
}
When I retrieve eventId from referringParams I have to navigate the user to the specific event. When I was using Navigation components with fragments I could just do:
findNavController(R.id.navHost).navigate("path to event screen")
But with compose is different because I can't use navController outside of Composable since its located in MainActivity#onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//I cant access navController outside of composable function
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "HomeScreen",
) {
}
}
}
My question is, how can I navigate the user to a specific screen from MainActivity#onStart() when using jetpack compose navigation
rememberNavController has pretty simple implementation: it creates NavHostController with two navigators, needed by Compose, and makes sure it's restored on configuration change.
Here's how you can do the same in your activity, outside of composable scope:
private lateinit var navController: NavHostController
private val navControllerBundleKey = "navControllerBundleKey"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navController = NavHostController(this).apply {
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
savedInstanceState
?.getBundle(navControllerBundleKey)
?.apply(navController::restoreState)
setContent {
// pass navController to NavHost
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBundle(navControllerBundleKey, navController.saveState())
super.onSaveInstanceState(outState)
}

Navigation component: Retain same fragment after orientation change

as I said in the title, I would like to keep my current fragment after rotating the phone. I could actually make it happen by adding
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
in the Manifest. The problem is that my layout configuration is lost every time.
What am I doing wrong? How can I fix it?
I'm using Jetpack Navigation component. Exist a way to navigateToCurrentFragment()?
Edit: Part of onCreate() from Activity()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
val graphInflater = (supportFragmentManager.findFragmentById(R.id.mNavHost) as NavHostFragment).navController.navInflater
val navGraph = graphInflater.inflate(R.navigation.services_nav_graph)
navGraph.startDestination = R.id.FirstFragment
(supportFragmentManager.findFragmentById(R.id.mNavHost) as NavHostFragment).navController.graph = navGraph
}

Hide android bottom navigation view for child screens/ fragments

I'm trying to create a single activity Android application.
I have MainActivity (only activity) with BottomNavigationView, three top level fragments and some child fragments. My requirement is whenever the screen is showing top level fragments, bottom navigation should be visible such that switching is possible. But when I'm viewing any of the child fragments, bottom navigation should be hidden.
Is there any out-of-box way using the Navigation component or need to change the visibility manually ?
Update (Navigation component 1.0)
As of Navigation component 1.0.0-alpha08, method addOnNavigatedListener(controller: NavController, destination: NavDestination) was changed to addOnDestinationChangedListener(controller: NavController, destination: NavDestination, arguments: Bundle). Its behavior was also slightly changed (it is also called if the destinations arguments change).
Old Answer
You can use NavController.OnNavigatedListener to achieve this behavior (set it in Activity onCreate):
findNavController(R.id.container).addOnNavigatedListener { _, destination ->
when (destination.id) {
R.id.dashboardFragment -> showBottomNavigation()
else -> hideBottomNavigation()
}
}
private fun hideBottomNavigation() {
// bottom_navigation is BottomNavigationView
with(bottom_navigation) {
if (visibility == View.VISIBLE && alpha == 1f) {
animate()
.alpha(0f)
.withEndAction { visibility = View.GONE }
.duration = EXIT_DURATION
}
}
}
private fun showBottomNavigation() {
// bottom_navigation is BottomNavigationView
with(bottom_navigation) {
visibility = View.VISIBLE
animate()
.alpha(1f)
.duration = ENTER_DURATION
}
}
Using addOnDestinationChangedListener works, and it's the solution recommended in the official documentation, but it does cause some flickering, as the callback is executed before the fragment is attached.
I find the below answer more flexible, and handles animations better:
supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
TransitionManager.beginDelayedTransition(binding.root, Slide(Gravity.BOTTOM).excludeTarget(R.id.nav_host_fragment, true))
when (f) {
is ModalFragment -> {
binding.bottomNavigation.visibility = View.GONE
}
else -> {
binding.bottomNavigation.visibility = View.VISIBLE
}
}
}
}, true)
You can customize it depending on the transitions between your fragments, by choosing different animation (on my example it's a Slide), or by making the call at another lifecycle callback.
You have to make a method in MainActivity for visibility. Do call that method from fragments where you want to show or hide.
One thing I faced with such scenario is, bottom navigation visibility is not being properly gone. So I put bottom navigation view in Relative layout and hide that parent view.
you just need to write this code in MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Getting the Navigation Controller
navController = Navigation.findNavController(this, R.id.fragment)
//Setting the navigation controller to Bottom Nav
bottomNav.setupWithNavController(navController)
//Setting up the action bar
NavigationUI.setupActionBarWithNavController(this, navController)
//setting the Bottom navigation visibiliy
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.id == R.id.full_screen_destination ){
bottomNav.visibility = View.GONE
}else{
bottomNav.visibility = View.VISIBLE
}
}
}
get more details from the android developer documentation:
Update UI components with NavigationUI
So even tho this question was already answered and the accepted answer is one that works, here is the code to actually achieve this behaviour:
MainActivity
fun hideBottomNav() {
bottomNavigationView.visibility = View.GONE
}
fun showBottomNav() {
bottomNavigationView.visibility = View.VISIBLE
}
Then you call the functions in your fragment onViewCreated(), onDetach() function, like:
Fragment
class FragmentWithOutBottomNav() : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as MainActivity).hideBottomNav()
}
override fun onDetach() {
super.onDetach()
(activity as MainActivity).showBottomNav()
}
}
Hope I could help some people. Happy coding!
navController.addOnDestinationChangedListener { _, destination, _ ->
val isMainPage = bottomNavigationView.selectedItemId == destination.id
bottomNavigationView.isVisible = isMainPage
}

Activity content transition doesn't work

I want to start Activity with content transition. But the content transition doesn't work. Why and how to fix it?
This is my code:
class PolishStartActivity : AbsActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initContentTransition()
setContentView(R.layout.activity_polish_start)
}
private fun initContentTransition() {
window.apply {
requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
enterTransition = TransitionSet().apply {
addTransition(Slide(Gravity.TOP).addTarget(R.id.tvBooksTitle).addTarget(R.id.tvBooksDescription))
addTransition(Fade().addTarget(R.id.cvSearchBox))
// addTransition(Fade()) only this transition works fine
duration = 1000L
}
}
}
}
P.S. I start my Activity with ActivityOptions.
After many attempts and experiments, I found the fix for this problem.
On my root ViewGroup (in my case it is ConstraintLayout) add this line:
android:transitionGroup="false"
Yes! Now you can set targets for transitions and it will be work!
Put initContentTransition() before super.onCreate();
class PolishStartActivity : AbsActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
initContentTransition()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_polish_start)
}
..................
..................
}

Categories

Resources