2

I'm using jetpack navigation to transition from a fragment to a detail fragment.

I need help presenting the detail fragment over the fragment that displays it.

See photo below to look what I am trying to achieve:

Eiffel Tower

I'm also using a basic fade_in / fade_out for Enter Exit on the Animations within the nav graph.

Here's the result I am getting:

result of presenting view not showing up in the detail view

How can I set up the transition so that the detail fragment displays over the presenting view?

Here's all my progress:

bottom_nav_menu

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
            android:id="@+id/fragment_tab1"
            android:title="Tab 1"/>

    <item
            android:id="@+id/fragment_tab2"
            android:title="Tab 2"/>

    <item
            android:id="@+id/fragment_tab3"
            android:title="Tab 3"/>

</menu>

nav_graph

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph"
            app:startDestination="@id/fragment_tab1">
    <fragment android:id="@+id/fragment_tab1" android:name="com.example.navgraphfragmentmodal.Tab1" android:label="fragment_tab1"
              tools:layout="@layout/fragment_tab1">
        <action android:id="@+id/toModal" app:destination="@id/modalFragment"/>
    </fragment>
    <fragment android:id="@+id/fragment_tab2" android:name="com.example.navgraphfragmentmodal.Tab2" android:label="fragment_tab2"
              tools:layout="@layout/fragment_tab2"/>
    <fragment android:id="@+id/fragment_tab3" android:name="com.example.navgraphfragmentmodal.Tab3"
              android:label="fragment_tab3" tools:layout="@layout/fragment_tab3"/>
    <fragment android:id="@+id/modalFragment" android:name="com.example.navgraphfragmentmodal.ModalFragment"
              android:label="fragment_modal" tools:layout="@layout/fragment_modal"/>
</navigation>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val navHostFragment = supportFragmentManager.findFragmentById(R.id.navigation_host_fragment) as NavHostFragment?
        NavigationUI.setupWithNavController(bottom_navigation_view, navHostFragment!!.navController)

        supportActionBar?.setDisplayShowHomeEnabled(true)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        supportActionBar?.setDisplayShowHomeEnabled(false)
        if (item.itemId === android.R.id.home) {
            //Title bar back press triggers onBackPressed()
            onBackPressed()
            return true
        }
        return super.onOptionsItemSelected(item)
    }

    //Both navigation bar back press and title bar back press will trigger this method
    override fun onBackPressed() {
        supportActionBar?.setDisplayShowHomeEnabled(false)
        if (supportFragmentManager.backStackEntryCount > 0) {
            supportFragmentManager.popBackStack()
        } else {
            super.onBackPressed()
        }
    }
}

Tab1 Fragment (This is the fragment with the Eiffel Tower image & button)

class Tab1 : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_tab1, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        learnMoreButton.setOnClickListener {
            NavHostFragment.findNavController(this).navigate(R.id.modalFragment)
        }
    }

}

Bounty edit:

I am looking to display a fragment over a fragment in a navigation graph, as seen in the Eiffel Tower image. In iOS this would be similar to presentation: Over Full Screen. I do not wish to use a DialogFragment, as I have far more control over animating the views in a normal Fragment.

Joe
  • 3,772
  • 3
  • 33
  • 64
  • What if you create details Fragment as a DialogFragment? – Deˣ Jun 12 '19 at 16:12
  • It's a simple AlertDialog that you need looking at your picture. – Vedprakash Wagh Jun 12 '19 at 16:33
  • @Derek & Vedprakash Wagh, I changed the detail fragment to a DialogFragment, but that didn't change how the content is displayed. It still presents in a new view without the background of the parent view. – Joe Jun 12 '19 at 16:56
  • @Joe post your xml layout for details activity. – Deˣ Jun 12 '19 at 17:06
  • I don't get it, are you saying that you are in an activity, that is displaying a Fragment (the eiffel tower's image) and then you want to display a dialog on top of said fragment and you can't? Navigation version? Something doesn't look right. If you suspect this is a bug I recommend [filing an issue](https://issuetracker.google.com/issues/new?component=409828) – Martin Marconcini Jun 12 '19 at 18:07
  • @Martin Marconcini - I have a navigation graph setup with the jetpack navigation component. The project is comprised of a bottom navigation with 3 tabs. The Eiffel Tower is in tab 3 (all 3 tabs are fragments). All I want to do is open the detailed fragment (the little modal that displays some landmark information) *over* tab 3. I have this working perfectly in a project that doesn't use the Nav graph, but uses intents instead. I'm trying to figure out how to achieve this using findNavController().navigate. – Joe Jun 12 '19 at 18:13
  • 2
    Gotcha, well, maybe you're lucky (I added the android-navigation tag) and a Google Employee can help you here, since I haven't played _that deep_ into Navigation (only use it to go from fragment a ->b and so froth) no magic happening. But I'd be interested to hear what Ian (from Google) has to say. :) – Martin Marconcini Jun 12 '19 at 18:16
  • Can you post xml code of your navigation graph? – Natig Babayev Jun 18 '19 at 08:44
  • @Natig Babayev I posted the XML of my nav graph – Joe Jun 18 '19 at 16:21

1 Answers1

9

You are trying to add a modal dialog to a navigation graph as a normal destination. NavController will always only show one destination at a time and they are not stacked. A fragment is removed from the view when an other one is shown by NavController. It's not intended to function in the way you want it to do.

You need to create a AlertDialog or an DialogFragment with your UI. In the case shown above a AlertDialog will already be sufficient. You can add a custom view to an AlertDialog like this:

val dialog = AlertDialog.Builder(context)
    .setView(R.layout.your_layout)
    .show()

dialog.findViewById<View>(R.id.button).setOnClickListener {
   // Do somehting
   dialog.dismiss()
}

This allows you to create a dialog with your custom view. It will work like one would want it to work: Clicking the button closes the dialog as well as pressing the back button. The background will automatically be dimmed.

The dialog will have the default shape but you can follow this answer to create the rounded corners of the dialog shown in your question.

You can add animations to the AlertDialog with window animations and a custom style, refer to this answer.

For more complex views requiring a lifecycle DialogFragment should be used (e.g. when showing a map in the dialog). Starting with navigation 2.1.0-alpha03 <dialog>destinations are supported. This is still in alpha/beta though.

Edit:

If you want extremely fine control over animations you can add a view to your layout like this:

<FrameLayout>

   <!-- Remove the defaultNavHost from this! -->
   <fragment android:id="@+id/navHost" android:name="...NavHost" />

   <FrameLayout android:id="@+id/dialog" android:background="#44000000" android:layout_width="match_parent" android:layout_height="match_parent">

       <FrameLayout android:id="@+id/dialogContent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center">

           <!-- Dialog contents -->

       </FramyLayout>

   </FrameLayout>

</FrameLayout>

Because the item is after the nav host in the layout, it will be rendered on top of it. Be aware that buttons or other elements having the elevation property set may appear on top of it.

Finally, overwrite your onBackPressed() in your activity to restore the navigation behaviour:

override fun onBackPressed() {
    val dialog = findViewById<View>(R.id.dialog)
    val navController = (supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment).navController
    if (dialog.visibility == View.VISIBLE) {
        hideDialog()
    } else if(navController.popBackStack()) {
        Log.i("MainActivity", "NavController handled back press")
    } else {
        super.onBackPressed()
    }
}

private fun showDialog() {
    findViewById<View>(R.id.dialog).visibility = View.VISIBLE
    // Intro animation
}

private fun hideDialog() {
    // Outro animation. Call the next line after the animation is done.
    findViewById<View>(R.id.dialog).visibility = View.GONE
}

This might be what you are looking for but I'd not recommend it and work with AlertDialog or DialogFragment instead.

You could also use a new Activity with a transparent background for the dialog and disable the animations on this activity so you can manually animate the views in the activity. Activities can be 100% transparent.

crysxd
  • 3,177
  • 20
  • 32