Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Tutorials about Navigation Components to learn using nav graphs, adding top menus, passing arguments via safe args, combining with different Material Design widgets such as BottomNavigationView, Toolbar, ViewPager2, TabLayout and dynamic feature module navigation with DynamicNavHostFragment and examining Memory Leaks.

License

NotificationsYou must be signed in to change notification settings

SmartToolFactory/NavigationComponents-Tutorials

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tutorials for learning Navigation Components starting with simple set up, continues with adding top menus, passing arguments via navigation graphs and combining them with different Material Design widgets such asBottomNavigationView,Toolbar,ViewPager2,TabLayout anddynamic feature module navigation withDynamicNavHostFragment.

Overview

Tutorial Coverage

Covers basic BottomNavigationView and ViewPager usage without any navigatiom components. This is a little bit like warm up before moving to more complex ones including navigation with ViewPager2

Covers how to use create navigation graphnav_graph.xml inside navigation folder, andNavHostFragmentinactivity_main.xml layout file.

Note

One important note to add is navigation uses FragmentTransaction.replace() to navigate next fragment inside specifiedNavHostFragment

    <androidx.fragment.app.FragmentContainerView        android:id="@+id/nav_host_fragment"        android:name="androidx.navigation.fragment.NavHostFragment"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:defaultNavHost="true"        app:navGraph="@navigation/nav_graph" />

There are multiple ways to navigate from one fragment to another using aNavController, for instance

buttonCenter?.setOnClickListener {    val options = navOptions {        anim {            enter = R.anim.slide_in_right            exit = R.anim.slide_out_left            popEnter = R.anim.slide_in_left            popExit = R.anim.slide_out_right        }    }    findNavController().navigate(R.id.middle1_dest, null, options)}

Check out this tutorial if you wish to get familiar with basic consepts, animation and navigating withpopUpToandpopUpToInclusive

Uses seperate and nested fragments with each it's own navigation graph.

nav_graph is the main graph for navigation has navigation toCameraFragment or other graphs such asnav_graph_dashboard ornav_graph_homeNested graphs are part of the samenavHostFragment?.childFragmentManager

Covers how nested graphs with their own back stack orNavHostFragment work. You can check out both main and home childFragmentManager back stack entry and fragment count by examining Toast or log messages.

Note

Main graph back stack is controlled byNavHostFragment.getChildFragmentManager

When a nested navigation graph orNavHostFragment is added it's back stack is retrievedusing a childFragmentManager.

When a fragment is from a nested navigation graph is on screen when you navigate back or forth it's current positionchanges only for the current NavHostFragment, main back stack does not change.

val callback = object : OnBackPressedCallback(false) {   override fun handleOnBackPressed() {       // Get NavHostFragment       val navHostFragment =           childFragmentManager.findFragmentById(nestedNavHostFragmentId)       // ChildFragmentManager of the current NavHostFragment       val navHostChildFragmentManager = navHostFragment?.childFragmentManager       // Check if it's the root of nested fragments in this navhosts       if (navController?.currentDestination?.id == navController?.graph?.startDestination) {           /*               Disable this callback because calls OnBackPressedDispatcher                gets invoked  calls this callback  gets stuck in a loop            */           isEnabled = false           requireActivity().onBackPressed()           isEnabled = true       } else if (isVisible) {           navController?.navigateUp()       }   }}

Note

Back navigation does NOT work for the first fragment in back stack ofHomeNavHostFragment for this example, because

if (navController!!.currentDestination == null || navController!!.currentDestination!!.id == navController!!.graph.startDestination) {   navController?.navigate(R.id.homeFragment1)}

and start destination isHomeNavHostFragmentitself , but last fragment, currentDestination on graph isHomeFragment1 when back button is pressed whileHomeFragment2 is on screen.

Changeapp:startDestination="@id/home_dest" toapp:startDestination="@id/homeFragment1" to solve back press issue forHomeNavHostFragment, it's just set to demonstrate how start destination change back press.

<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_home"      app:startDestination="@id/home_dest">  <fragment          android:id="@+id/home_dest"          android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.navhost.HomeNavHostFragment"          android:label="HomeHost"          tools:layout="@layout/fragment_navhost_home" />  <fragment          android:id="@+id/homeFragment1"          android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment1"          android:label="HomeFragment1"          tools:layout="@layout/fragment_home1">  </fragment></navigation>

Navigation Architecture

MainActivity (Appbar + Toolbar)|- MainNavHost    |    | FragmentViewPagerContainer(ViewPager2 + TabLayout)        |- HomeFragment1 -> HomeFragment2 -> HomeFragment3        |- DashboardFragment1 -> DashboardFragment2 -> DashboardFragment3

Covers how to create aViewPager2with navigation in main back stack, in this exampleViewPager2pages do not have their own back stacks. It's covered in tutorial Tutorial6-2.

Same as previous tutorial except only with one difference,data binding is used for binding.

Note

Data binding that is not null(or non-nullable) afterFragment.onDestroyView whenViewPager2is inside a fragment causing leak canary to show data binding relatedMEMORY LEAK for this fragment when used inViewPager2. Also you need to set adapter of ViewPager2 either to prevent memory leaks, and another one is due to TabLayouts which is covered in later sections. Check out thisstackoverflow question for more details.

Navigation Architecture

 MainActivity (Appbar + Toolbar)    |- MainNavHost       |       |- ViewPagerContainerFragment(ViewPager2 + TabLayout)       |   |- HomeNavHostFragment       |   |  |- HF1 -> HF2 -> HF3       |   |       |   |- DashboardNavHostFragment       |   |  |- DF1 -> DF2 -> DF3       |   |       |   |- NotificationHostFragment       |   |  |- NF1 -> NF2 -> NF3       |   |       |   |-LoginFragment1       |       |- LoginFragment1 -> LoginFragment2

CoversViewPager2 and it's pages each with it's own back stack or navigation graphs.

NavHostFragment use NavController to navigate back/forth in any page.

Note

This tutorial has very important aspects forViewPager2 navigation

  1. CreatingNavHostFragment for each page and can navigate inside them, each page hasit's own nav graph.

    in each layout fileNavHostFragment inside is retrieved using

            val nestedNavHostFragment =            childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment        navController = nestedNavHostFragment?.navController`

    HomeNavHostFragment uses the first fragment that is displayed on screen **HomeFragment1 whileDashboardNavHostFragment uses graph with itself as start destination so it should check for theNavController.getCurrentDestination() to navigate to it when device rotated

    LoginFragment1 is added to main graph, because of that appbar back navigation only works with theViewPagerContainerFragment'sNavController

  2. How to use back navigation withOnBackPressedCallback, there is an alternative and more simple wayto handle back navigation forViewPager2 but this also a way to keep in mind if more customization is required.If you do not handle back navigation Activity's back press gets called and application starts from onCreate.

  3. Checking out memory leaks with data binding, ViewPager2 adapter and lifecylce.

    • You should setdata binding tonull or you will get memory leaks for this ViewPager2 which is itself also inside a fragment

    • You should set ViePager2's adapter to null inonDestroyView

    • 🔥 You should useChildFragmentStateAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle), notthe one that takesFragment as parameter.And useview's lifecycle instead of setting Fragment's lifecycle.

    viewPager.adapter =                   ChildFragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)

Refer to thisstackoverflow question for more details.

Navigation Architecture

 MainActivity(Appbar + Toolbar + TabLayout + ViewPager2)   |   |- HomeNavHostFragment   |  |- HF1 -> HF2 -> HF3   |   |- DashboardNavHostFragment   |  |- DF1 -> DF2 -> DF3   |   |- NotificationHostFragment      |- NF1 -> NF2 -> NF3

In this tutorial MainActivity has it's appbar that navigation is controlled using theNavController retrieved fromNavHostFragment viaLiveData

Note

There is an issue with rotation, when device rotated ActivityFragmentStateAdapter.createFragment method is not called and it's not possible to accessNavController of newly created fragments. If you do not wish to have a rotatable appyou can use live data or ViewModel to get currentNavController to change appbar title and get otherproperties ofNavController. LiveData is observed inMainActivity to set appbar title

Navigation Architecture

 MainActivity    |- MainNavHost       |       |- ViewPagerContainerFragment(ViewPager)           |           |- HomeNavHostFragment(Appbar + Toolbar)           |  |- HF1 -> HF2 -> HF3           |           |- DashboardNavHostFragment(Appbar Toolbar)           |  |- DF1 -> DF2 -> DF3           |           |- NotificationHostFragment(Appbar Toolbar)              |- NF1 -> NF2 -> NF3

In this tutorial eachNavHostFragment has it's own toolbarThey can navigate back with back arrow when navigated to an inner/nested fragment in pages of ViewPager

UsingFragmentStateAdapter.registerFragmentTransactionCallback withFragmentStateAdapter solves back navigation instead of usingOnBackPressedCallback.handleOnBackPressed in everyNavHostFragment as answeredhere

init {    // Add a FragmentTransactionCallback to handle changing    // the primary navigation fragment    registerFragmentTransactionCallback(object : FragmentTransactionCallback() {        override fun onFragmentMaxLifecyclePreUpdated(            fragment: Fragment,            maxLifecycleState: Lifecycle.State        ) = if (maxLifecycleState == Lifecycle.State.RESUMED) {            // This fragment is becoming the active Fragment - set it to            // the primary navigation fragment in the OnPostEventListener            OnPostEventListener {                fragment.parentFragmentManager.commitNow {                    setPrimaryNavigationFragment(fragment)                }            }        } else {            super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)        }    })}

If defaultNavHost is not set to true true for anyNavHostFragments by settingapp:defaultNavHost="true" in xml or programmaticallysnippet above will not work.

Navigation Architecture

MainActivity   |- MainNavHost      |      |- ParenNavHost((Appbar + Toolbar)          |          |- ViewPagerContainerFragment(ViewPager2)          |   |          |   |- HomeNavHostFragment(Appbar + Toolbar)          |   |  |- HF1 -> HF2 -> HF3          |   |          |   |- DashboardNavHostFragment(Appbar + Toolbar)          |   |  |- DF1 -> DF2 -> DF3          |   |          |   |- NotificationHostFragment(Appbar + Toolbar)          |   |  |- NF1 -> NF2 -> NF3          |   |          |   |-LoginFragment1          |          |- LoginFragment1 -> LoginFragment2

In this tutorial eachNavHostFragment has it's own toolbar, alsoParentNavHostFragment has it's own toolbar either.

LoginFragment2 in this example is added to back stack of ParentNavHostFragment because of that it does not have any association with toolbar inViewPagerContainerFragment

ParentNavHostFragment's role is to have it's own Appbar to contain login fragments and navigate through them using Appbar. WithoutParentNavHostFragment we navigate toLoginFragment2 that has no Appbar.

Visibility ofParentNavHostFragment is changed via liveData ofAppbarViewModel

However, there is an issue whenever Toolbar that is not belong to fragments appear or disappear.

Navigation Architecture

MainActivity  |- MainNavHostFragment     |     |- ParenNavHostFragment(Appbar + Toolbar) Here because we wish to have toolbar inside Fragment         |         |- ViewPagerContainerFragment(TabLayout + ViewPager2)         |   |         |   |- HomeNavHostFragment         |   |  |- HF1 -> HF2 -> HF3         |   |         |   |- DashboardNavHostFragment         |   |  |- DF1 -> DF2 -> DF3         |   |         |   |- NotificationHostFragment         |   |  |- NF1 -> NF2 -> NF3         |   |         |   |-LoginFragment1         |         |- LoginFragment1 -> LoginFragment2

In this tutorial, onlyParentNavHostFragment has Appbar and Toolbar. Navigation of individualNavHostFragments is done via LiveData belong toAppbarViewModel.currentNavController whichreturnsNavController of currentNavHostFragment on screen due. CurrentNavController is setonResume to make sure toset it only when the current fragment is visible, it's instead of checking if fragment is on screen and visible.

ParentNavHostFragment's role is to have it's own Appbar to contain login fragments and navigate through them using Appbar. Without ParentNavHostFragment we navigate toLoginFragment2 that has no Appbar if it's insideViewPagerContainerFragment.

It can be done by putting Appbar toMainActivity but purpose here is to putAppbar + Toolbar inside a fragment to be able to use withBottomNavigationView for instance

Navigation Architecture

 MainActivity(BottomNavigationView + ViewPager2 + Appbar + Toolbar)       |- HomeNavHostFragment       |   |- HF1 -> HF2 -> HF3       |       |- DashboardNavHostFragment       |   |- DF1 -> DF2 -> DF3       |       |- NotificationHostFragment       |   |- NF1 -> NF2 -> NF3

In this exampleBottomNavigationView selects which page ofViewPager2 should be opened usingBottomNavigationView.setOnNavigationItemSelectedListener

Appbar title is changed using LiveDataAppbarViewModel.currentNavController of visibleNavHostFragment on screen of ViePager2 page

Change current nav controller to set appbar title

appbarViewModel.currentNavController.observe(this, Observer { it ->    it?.let { event: Event<NavController> ->        event.getContentIfNotHandled()?.let { navController ->            val appBarConfig = AppBarConfiguration(navController.graph)            dataBinding.toolbar.setupWithNavController(navController, appBarConfig)        }    }})

Navigation Architecture

     MainActivity(BottomNavigationView + ViewPager2 + Appbar + Toolbar)        |        |- ViewPagerContainerFragment(TabLayout + ViewPager2)        |      |        |      |- HomeNavHostFragment        |      |  |- HF1 -> HF2 -> HF3        |      |        |      |- DashboardNavHostFragment        |      |  |- DF1 -> DF2 -> DF3        |      |        |      |- NotificationHostFragment        |      |  |- NF1 -> NF2 -> NF3        |      |        |      |-LoginFragment1        |        |- DashboardNavHostFragment        |   |- DF1 -> DF2 -> DF3        |        |- NotificationHostFragment        |   |- NF1 -> NF2 -> NF3

This example is combination of Tutorial6-6 and Tutorial 7-1

First tab of theBottomNavigationView isViewPagerContainerFragment which has aViewPager2 that has it's own pages with each it's own back stack settingNavController is done both usingAppbarViewModel andBottomNavigationView.setupWithNavController in the NavigationExtensions code for setting BottomNavigationView back stack.

Navigation Architecture

  MainActivity    |- MainNavHostFragment         |         |- ContainerNavHostFragment(BottomNavigationView  Appbar + Toolbar)         |    |         |    |- ViewPagerContainerFragment(ViewPager2 + TabLayout)         |    |      |         |    |      |- PostVerticalNavHost         |    |      |  |- PostVerticalFragment -> PostDetailFragment         |    |      |         |    |      |- PostHorizontalNavHost         |    |      |  |- PostHorizontalFragment -> PostDetailFragment         |    |      |         |    |      |- PostGridNavHostFragment         |    |      |  |- PostGridFragment         |    |      |         |    |      |- PostStaggerNavHostFragment         |    |      |  |- PostStaggeredFragment         |    |      |         |    |      |- NotificationHostFragment         |    |      |  |- NF1 -> NF2 -> NF3         |    |      |         |    |      |- LoginFragment1         |    |         |    |         |    |- DashboardNavHostFragment         |    |   |- DF1 -> DF2 -> DF3         |    |         |    |- NotificationHostFragment         |    |  |- NF1 -> NF2 -> NF3         |    |         |    |- &PostGridFragment -> PostDetailFragment         |    |         |    |- &LoginFragment1 -> LoginFragment2         |         |         |- &PostStaggeredFragment -> PostDetailFragment

In this tutorialBottomNavigationView is insideMainFragment.MainActivity can navigate to a different fragment other than theMainFragment using nav_graph main destinations.

Navigation is layered, fragments annotated with& display that hey navigate at that level, not actually added to that hierarchy.

For instance,PostStaggeredFragment which is inViewPager2 calls snippet below to get mainNavController to navigate in main navigation graphrequireActivity().findNavController(R.id.main_nav_host_fragment).navigate(R.id.action_mainFragment_to_postDetailFragment, bundle)

PostGridFragment which is inViewPager2 getsPNavControllerP that belong toViewPagerContainerFragment viaparentFragment?.parentFragment?.findNavController() and navigates fromViewPagerContainerFragment toPostDetailFragment

🔥🔥🔥 Important

If you navigate fromPostStaggeredFragment toPostDetailFragment fragment you will see that memory leak occurs. It happens due to BottomNavigationView.setupWithNavControllerfunction inNavigationExtensions.ktclass leaking because of listeners not being unregistered in extension functions inonDestroyView``` of fragment.

Check out Tutorial8-2 for solution for this issue

This tutorial to navigate to dynamic feature module from App, navigate from dynamic feature module gallery to favorites.

Steps to create dynamic navigation from app to dynamic feature modules, or from one dynamic feature module to another

  1. Replaceandroid:name="androidx.navigation.fragment.NavHostFragment" withandroid:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment" for the layouts
<androidx.fragment.app.FragmentContainerView    android:id="@+id/nestedDashboardNavHostFragment"    android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"    app:defaultNavHost="true"    app:navGraph="@navigation/nav_graph_dashboard"/>
  1. Add dynamic navigation with<include>,<activity> or as fragmentt to any navigation graph
    • You don't have to use an id, but if you do, same id defined here must be used in dynamic feature module, or will get an error.
    • Add package name of dynamic feature module
    • Add nav graph name in navigation folder of dynamic feature module
    • Add module name which is also in app's build.gradle file
    • Optionally add arguments to pass to dynamic feature module fragments
<!-- gallery dynamic feature module--><include-dynamic    android:id="@+id/nav_graph_gallery"    android:name="com.smarttoolfactory.gallery"    app:graphResName="nav_graph_gallery"    app:moduleName="gallery">    <argument        android:name="count"        android:defaultValue="0"        app:argType="integer" /></include-dynamic>
  1. In dynamic feature module navigation creata navigation graph to navigate in feature module fragmnent or navigate to other dynamic feature modueles, and passing arguments and getting the result from a fragment withsavedStateHandle.
    • id in dynamic feature module should not contain+, because you are using the same id resource defined in app or the snippet above. If you do you will have the error i gothere
<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@id/nav_graph_gallery"    app:moduleName="gallery"    app:startDestination="@id/galleryFragment1">    <fragment        android:id="@+id/galleryFragment1"        android:name="com.smarttoolfactory.gallery.GalleryFragment1"        android:label="GalleryFragment1">        <action            android:id="@+id/action_galleryFragment1_to_galleryFragment2"            app:destination="@id/galleryFragment2" />        <action            android:id="@+id/action_galleryFragment1_to_nav_graph_favorites"            app:destination="@id/nav_graph_favorites" />    </fragment>    <!-- favorite dynamic feature module-->    <include-dynamic        android:id="@id/nav_graph_favorites"        android:name="com.smarttoolfactory.favorites"        app:graphResName="nav_graph_favorites"        app:moduleName="favorites" >        <argument            android:name="count"            app:argType="integer"            android:defaultValue="0" />    </include-dynamic></navigation>

HomeFragment1 listensavedStateHandle withfindNavController().currentBackStackEntry?.savedStateHandle.getLiveData<Int>

HomeFragment2 and GalleryFragment sets result withfindNavController().previousBackStackEntry?.savedStateHandle?.set("count", count)

Navigation Architecture

  MainActivity    |- MainNavHostFragment         |         |- ContainerNavHostFragment(BottomNavigationView  Appbar + Toolbar)         |    |         |    |- ViewPagerContainerFragment(ViewPager2 + TabLayout)         |    |      |         |    |      |- PostVerticalNavHost         |    |      |  |- PostVerticalFragment -> PostDetailFragment         |    |      |         |    |      |- PostHorizontalNavHost         |    |      |  |- PostHorizontalFragment -> PostDetailFragment         |    |      |         |    |      |- PostGridNavHostFragment         |    |      |  |- PostGridFragment         |    |      |         |    |      |- PostStaggerNavHostFragment         |    |      |  |- PostStaggeredFragment         |    |      |         |    |      |- NotificationHostFragment         |    |      |  |- NF1 -> NF2 -> NF3         |    |      |         |    |      |- LoginFragment1         |    |         |    |         |    |- DashboardNavHostFragment         |    |   |- DF1 -> DF2 -> DF3         |    |         |    |- NotificationHostFragment         |    |  |- NF1 -> NF2 -> NF3         |    |         |    |- &PostGridFragment -> PostDetailFragment         |    |         |    |- &LoginFragment1 -> LoginFragment2         |    |         |    |- NF1 | 2F2 -> Camera Dynamic Feature         |         |         |- DF1 | 2F2 -> Photo Dynamic Feature         |         |- NF1 | 2F2 -> Camera Dynamic Feature         |         |- &PostStaggeredFragment -> PostDetailFragment         // Photos Dynamic feature navigate to Camera Dynamic feature         |- Photos Dynamic Feature -> Camera Dynamic Feature

In this tutorial same as Tutorial 7-3 navigation layered, and navigation from app module to dynamic features, or navigation fromPhotos dynamic feature moduletoCamera dynamic feature module is available.

There are two versions of main fragment.

One withMainFragment which usesBottomNavigationView extension, andMainFragmentWithViewPager usesViewPager2 to create back stack ofBottomNavigationView.

Note

🔥 SinceDynamicNavHostFragment used instead ofNavHostFragment another extension should be written that creates fragments that have [DynamicNavHostFragment] in fragments.

IfBottomNavigationView extension is used withDynamicNavHostFragment it returns the error below

  Caused by: java.lang.IllegalStateException: Could not find Navigator with name "include-dynamic". You must call NavController.addNavigator() for each navigation type.     at androidx.navigation.NavigatorProvider.getNavigator(NavigatorProvider.java:98)     at androidx.navigation.NavInflater.inflate(NavInflater.java:107)     at androidx.navigation.NavInflater.inflate(NavInflater.java:141)     at androidx.navigation.NavInflater.inflate(NavInflater.java:88)

Also, since extension is not used, observe that not gettingMEMORY LEAK that was present in Tutorial7-3

ToDos:

  • Use nav graph ViewModel by navGraphViewModels, couldn't find it yet
  • SolvesetFragmentResultListener issue to get result from a framgent on back press, and use instead of savedStateHandle
  • Use saveStateHandle or save fragment state in Tutorial 8-2 when ViewPager2 moves away from viewPort
  • There is a performance issue in Tutorial 8-2 when navigate back from StaggerPostListFragment, or navigating back to fragment main
  • Use BottomNavigationView extension withDynamicNavHostFragment, or create another extension for dynamic feature modules

About

Tutorials about Navigation Components to learn using nav graphs, adding top menus, passing arguments via safe args, combining with different Material Design widgets such as BottomNavigationView, Toolbar, ViewPager2, TabLayout and dynamic feature module navigation with DynamicNavHostFragment and examining Memory Leaks.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp