美文网首页
Android开发(29)——Fragment和Navigati

Android开发(29)——Fragment和Navigati

作者: 让时间走12138 | 来源:发表于2021-05-16 15:28 被阅读0次

    本节内容

    1.简述

    2.Fragment的创建

    3.添加动画

    4.Fragment数据传递

    5.显示HomeFragment

    6.添加toolBar关联

    7.设置ButtonNavigationView

    8.抽屉布局drawLayout

    9.页面切换和数据传递

    一、简述
    1.对于MVVM来说,View指可交互的视图。ViewModel一般是管理数据的,通常是MutableLiveData。Model就是数据模块,通常需要一个Repository来联系Model和ViewModel。
    2.Activity:管理一个界面的生命周期。
    3.Fragment :可以理解为一个小型的Activity。一个手机app,用户在使用的时候,手机屏幕上只会显示一个页面,但是会有很多不同的页面进行切换。Fragment就相当于每一个小的页面,而管理这些页面的就是ViewPager。当用户滑动页面时,ViewPager就会把当前这个Fragment移出去,然后换下一个新的Fragment进来。
    二、Fragment的创建
    1.Fragment 表示应用界面中可重复使用的一部分。Fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。Fragment 不能独立存在,而是必须由 Activity 或另一个 Fragment 托管。Fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。
    2.FragmentActivity之间的关系:
    • Fragment依赖于Activity
    • Fragment 就是Activity中不同功能的分离,它有自己的生命周期。
    3.Fragment的生命周期。
    • onAttach():当Fragment被添加到activity中时调用
    • onCreate():创建Fragment时调用
    • onCreateView():Fragment具体显示的内容 -xml
    • onActivityCreated():当Activity创建好了之后,才会调用这个方法。相对Fragment进行操作的时候,就在这个方法里面操作。
    Fragment生命周期
    4.静态使用Fragment:
    • (1)在Activity对应的xml中添加Fragment组件
     <Fragment
            android:id="@+id/mContainer"
            android:name="com.example.fragment.LoginFragment"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    • (2)自定义一个类继承于fragment。比如我们创建一个类,名为LoginFragment,继承于Fragment,然后重写对应的生命周期方法。onCreateView方法必须实现。
    class LoginFragment :Fragment() {
         override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            return inflater.inflate(R.layout.fragmentlogin,container,false)
        }
    }
    
    • (3)创建xml文件 进行页面布局。创建一个fragmentlogin.xml文件,自己随便布局一下。
    • (4)使用LayoutInflater解析布局文件.
    return inflater.inflate(R.layout.fragmentlogin,container,false)
    
    • (5)在xml中设置fragment对应的name属性。在activity_xml的<Fragment>里面设置。
    android:name="com.example.fragment.LoginFragment"
    
    5.在activity.xml中添加几个按钮。最上方是fragment。
    xml布局
    5.动态使用Fragment。
    • (1)在Activity对应的xml中添加FrameLayout组件.
    <FrameLayout
            android:id="@+id/mContainer"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    • (2)自定义一个类继承于fragment(3)重写对应的生命周期方法 onCreateView(4)创建xml文件 进行页面布局(5)用layoutInflater解析布局文件
    • (6)使用FragmentManager管理fragment的切换, MainActivity中使用supportFragmentManager 获取FragmentManager,获取FragmentTransaction对象。
    val fragmentTransaction = supportFragmentManager.beginTransaction()
    fragmentTransaction.add(R.id.mContainer,LoginFragment())
    fragmentTransaction.commit()
    
    • mContainer是activtiy里面FrameLayout容器的id。
    运行程序,得到以下结果。
    运行结果
    6.新建一个Fragment,然后布局一下对应的xml文件。随便布局一下。
    7.然后在MainActivity里面实现一下mReplace的监听事件,当我们点击REPLACE按钮的时候,它就会直接替换。
     mReplace.setOnClickListener { 
    supportFragmentManager
    .beginTransaction()
    .replace(R.id.mContainer,RegistFragment("jack"))
    .commit()
            }
    
    点击replace按钮之后
    三、添加动画
    1.前面点击按钮完了之后,文本切换了,但是点击下方的返回按钮时并不会回到原来的页面,即刚刚那个fragment并不在栈里面。如果想要点击返回时回到之前的界面,那么可以添加一个.addToBackStack(null),这样就可以让它在栈里面了。
    我们通过实现add按钮的点击事件来展示一下是如何使用的。
     mAdd.setOnClickListener {
     supportFragmentManager
    .beginTransaction()
    .add(R.id.mContainer,RegistFragment("rose"))
    .addToBackStack(null)
    .commit()
            }
    
    • 这个函数的意思就是把它添加到回退栈里面。
    2.给这个页面切换添加动画效果,首先添加一个anim包,然后添加动画的资源文件。
    目录
    • slide_in.xml
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500">
        <translate android:fromXDelta="100%" android:toXDelta="0%"/>
    </set>
    
    • slide_out.xml
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500">
        <translate android:fromXDelta="0%" android:toXDelta="-100%"/>
    </set>
    
    • 在MainActivity里面用上这个动画。
    .setCustomAnimations(R.anim.slide_in,R.anim.pop_out)
    
    3.重新布局一下fragmentlogin页面,如下图所示。
    fragmentlogin页面
    4.重新布局一下activity_main.xml,把那几个按钮都删掉,只留下一个FrameLayout,然后把它的高设为0dp,宽度设为match_parent。
    5.实现上面LOGIN按钮的点击事件,要在LoginFragment类里面实现。重写一下这个类里面的onActivityCreated方法,然后在里面先获取子控件,再实现其点击事件。
       override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            val btn= view?.findViewById<Button>(R.id.mLogin)
            btn?.setOnClickListener {
                fragmentManager?.let {
                        it.beginTransaction()
                        .setCustomAnimations(R.anim.slide_in,R.anim.slide_out)
                        .replace(R.id.mContainer,RegistFragment("jack"))
                        .addToBackStack(null)
                        .commit()
                }
            }
        }
    
    6.在MainActivity里面使用一下supportFragmentManager
    supportFragmentManager
                .beginTransaction()
                .replace(R.id.mContainer,LoginFragment())
                .commit()
    
    7.如果这样的话,点击返回按钮并没有动画。想要点击返回按钮也有动画的话,那么就要再在anim包里面添加两个动画,然后把它们添加到.setCustomAnimations里面
    .setCustomAnimations(R.anim.slide_in,R.anim.slide_out,R.anim.pop_in,R.anim.pop_out)
    
    四、Fragment数据传递
    1.给ResgisterFragment添加一个构造方法
    class RegistFragment ( val name:String):Fragment()
    
    2.给fragmentregister.xml添加一个TextView,然后在ResgisterFragment类里面实现onActivityCreated方法
    override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            view?.findViewById<TextView>(R.id.mTextView)?.text  =name
        }
    
    3.在LoginFragmentonActivityCreated方法里面先把EditText里的内容解析出来。
    val nameTv = view?.findViewById<EditText>(R.id.mNameTv)
    
    • 然后在replace方法里面把解析到的内容传进去。
    .replace(R.id.mContainer,RegistFragment(nameTv?.text.toString()))
    
    • 这样数据就能传递过去了。但是一旦把手机旋转一下,就会报错。因为旋转屏幕之后,Activity重新走了一遍生命周期,所以Fragment也要重新创建一下。但是它重新创建的时候需要我们给它的构造函数添加一个参数,可是我们给不了。如果手机不支持屏幕旋转功能的话,那么就可以这样写。但是如果手机屏幕可以旋转,这样就不行。
    4.想要让屏幕旋转过来后数据也不会变的话,可以像下面一样操作。使用View Model,但是一般情况下都不会这么用。
    • 先创建一个MyViewModel类继承自ViewModel,然后在里面添加一个liveData类型的数据。
    class MyViewModel:ViewModel() {
        var fragment:MutableLiveData<Fragment>?=MutableLiveData()
    }
    
    • LoginFragment类里面的onActivityCreated方法里面,用rf来记录数据。
      override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
    
            val btn= view?.findViewById<Button>(R.id.mLogin)
            val nameTv = view?.findViewById<EditText>(R.id.mNameTv)
            btn?.setOnClickListener {
                val rf = RegistFragment()
                val bundle = Bundle()
                bundle.putString("name",nameTv?.text.toString())
                rf.arguments = bundle
    
                val act = activity as MainActivity
                act.viewModel.fragment?.value = rf
    
                fragmentManager?.let {
                       it.beginTransaction()
                        .setCustomAnimations(R.anim.slide_in,R.anim.slide_out,R.anim.pop_in,R.anim.pop_out)
                           .replace(R.id.mContainer,rf)
                        .addToBackStack(null)
                        .commit()
                }
            }
    
    • 然后在MainActivity先懒加载一个变量,在onCreate方法把它解析出来。然后判断它是不是空,如果是空的就把Fragment传递过去,否则就把它作为livedata进行监听。
    class MainActivity : AppCompatActivity() {
    lateinit var viewModel:MyViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
             viewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
            if (viewModel.fragment?.value==null){
                viewModel.fragment?.value = LoginFragment()
            }
           viewModel.fragment?.observe(this, Observer {
               supportFragmentManager
                   .beginTransaction()
                   .replace(R.id.mContainer,it)
                   .commit()
           })
       }
    }
    
    • 最后屏幕转过来数据也没有改变。
    屏幕旋转后
    5.传数据用arguments来传,但这还不是最好的写法。
    五、显示homefragment
    1.前情提要
    • NavController:管理界面切换。
    • NavGraph:导航图。定义好了界面之间的跳转。
    2.因为我们要实现在目的地之间传递数据,所以在gradel的dependencies里面添加以下代码。
    def nav_version = "2.3.1"
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    
    3.我们要生成适用于 Kotlin 独有的模块的 Kotlin 代码,那么就在第二个gradle里面添加以下代码。
    apply plugin: "androidx.navigation.safeargs.kotlin"
    
    4.先创建一个名为HomeFragment的类,继承自Fragment()。可以直接通过构造方法把页面id 传过来,这样就不需要在onCreateView里面再解析了。
    class HomeFragment :Fragment(R.layout.fragment_home){
    }
    
    5.创建一个和上面这个类匹配的xml文件,取名fragment_home。随意布局一下这个页面,我的如下图所示。给按钮添加一个id。
    fragment_home.xml布局
    6.创建一个navGraph来管理页面之间的跳转。新建一个navigation的包,然后再创建一个navigation的xml文件。
    文件目录
    • 在fragment_home.xml中关联一下HomeFragment类。
    tools:context="HomeFragment"
    
    • 在nav_graph.xml中把fragment_home.xml文件添加进来
    nav_graph界面
    7.完成这个新的demo我们需要完成以下工作:
    • Navigation Graph 导航图,管理fragment之间的逻辑关系
    • NavigationFragment 容器
    • NavController 导航控制器,控制页面之间的切换。
    8.我们已经完成了第一步,现在我们来完成第二步,添加容器。在activity_xml里面,拖动containers的NavHostFragment过来,然后选择nav_graph。
    拖动一个containers
    9.当它运行起来,显示的就是我们刚刚布局的那个页面。
    运行结果
    六、添加toolBar关联navVontroller
    1.在activity.xml中添加一个toolBar,并把fragment的顶部约束到toolBar的下方。
    <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorPurple"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="MissingConstraints" />
    
    2.然后我们在AndroidManifest.xml中把theme改为NoActionBar。这样就不会显示系统自带的Bar了。
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    
    3.在MainActivity里面添加一个导航控制器。
     private lateinit var navController:NavController
    
    4.显示toolBar里面的内容。
    • 在onCreate里面获取navController
         //获取NavHostFragment对象
           val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
           //通过这个对象获取对应的navController
            navController = navHostFragment.findNavController()
    
    • 用自己的toolbar作为默认ActionBar
    setSupportActionBar(toolBar)
    
    • 这样就可以显示工程的名字了,之前是只有一个toolBar,没有工程名字。
    显示工程名
    5.给我们自己的toolBar设置一下主题。
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    
    6.但是这样显示的只是工程名字,并不是当前fragment所对应的label。想要让它显示的是fragment对应的label的话,我们就要让ActionBar 和NavController产生关联。这样的话,当NavController切换的时候 会把对应的Fragment的label显示到actionbar
    setupActionBarWithNavController(navController)
    
    当前fragment的label为Home
    七、设置BottomNavigationView
    1.新建一个SearchFragment类继承自Fragment,再创建一个fragment_sreach的xml文件。然后随便布局一下。
    class SearchFragment :Fragment(R.layout.fragment_sreach){
    }
    
    2.在nav_graph里面把serach_fragment添加进去。在那之前记得在search_fragment里面把以下代码添加进去。
    tools:context=".SearchFragment"
    
    nav_graph.xml
    3.在activity_main.xml的底部添加导航条。然后自行约束一下。
    布局之后的结果
    4.这样添加的导航条什么内容都没有,要想在里面添加内容就需要一个menu。所以我们new一个resource file,类型为menu,然后在里面添加一个xml文件。
    menu文件
    5.在drawable里面右击一下,new一个vector Asset,这样就可以添加系统为我们提供的图片。search图标如下图所示:
    search图标
    6.menu里的xml文件代码如下图所示。id是nav_graph里面每个fragment的id。
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/homeFragment"
            android:title="Home"
            android:icon="@drawable/ic_home"/>
    
        <item android:id="@+id/searchFragment"
            android:title="Search"
            android:icon="@drawable/ic_search"/>
    </menu>
    
    7.在activity_main.xml中的BottomNavigationView里面添加以下代码,把menu加进去,这样它就会显示内容了。
    app:menu="@menu/bottom_nav_menu"
    
    BottomNavigationView显示的内容
    8.关联bottomNavigationView和NavController,在MainActivity里面。这样设置完了之后,点击search按钮就能自动切换页面了。
    bottom_nav_view.setupWithNavController(navController)
    
    Home主界面 点击search切换页面
    9.配置程序的顶层页面,顶层之间的切换,没有返回箭头。如果不添加以下代码。第二个页面的toolBar就会默认添加一个返回箭头,意思是把第一个页面作为主页面。这个意思就是让这两个页面处于同等地位,没有什么主次之分。
    val config:AppBarConfiguration = AppBarConfiguration(
            setOf(R.id.homeFragment,R.id.searchFragment)
        )
    setupActionBarWithNavController(navController,config)
    
    八、抽屉布局drawerLayout
    1.在activity_main.xml中,先把最外层设置为抽屉布局
    androidx.drawerlayout.widget.DrawerLayout
    
    • 在里面创建一个ConstraintLayout,然后把toolBar,BottomNavigationView,和中间的fragment都copy进去。
    2.在drawerLayout里面添加一个NavigationView,并设置id。gravity设为start,表示在左边。
     <com.google.android.material.navigation.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="start"/>
    
    • 这样运行起来就是以下效果。因为没有添加内容,所以是空的。
    加了NavigationView
    3.要想让它显示内容的话,我们就需要一个menu,直接把我们前面设置的menu加进去即可。
    app:menu="@menu/bottom_nav_menu"
    
    4.但是这样并不能点击,所以我们要关联NavigationView 和NavController
    nav_view.setupWithNavController(navController)
    
    • 这样旁边不仅有标签栏,而且还可以点击进行跳转。
    左侧菜单栏
    5.给这个菜单栏添加一个icon,这样点击图标也可以把它拉出来。只需要在前面的config里面再添加一下drawerLayout的id即可。
    val config:AppBarConfiguration = AppBarConfiguration(
            setOf(R.id.homeFragment,R.id.searchFragment),
            drawer_layout
        )
    
    • 添加之后icon图标也显示出来了
    icon图标
    6.但是此时的icon点击之后是没有效果的。想要点击它之后就弹出菜单的话,我们需要先把config定义在外部。
     private  lateinit var config:AppBarConfiguration
    
    • 然后在实现一下onSupportNavigateUp方法。
    override fun onSupportNavigateUp(): Boolean {
            return navController.navigateUp(config)
        }
    
    • 这样点击这个图标,菜单栏就会显示出来了。
    九、页面切换和数据传递
    1.添加一个login的fragment,包括代码和xml布局。代码如下图所示:
    class LoginFragment :Fragment(R.layout.fragment_login){
    }
    
    • xml就随便布局一下。然后设置一下context。再把它加进nav_graph。让homeFragment指向loginFragment。这样点击按钮,就会跳转到这个页面来。
    指向关系
    2.在HomeFragment里面给按钮添加点击事件。这样就可以直接切换。
    override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            button_login.setOnClickListener {
                val action:NavDirections= HomeFragmentDirections
                    .actionHomeFragmentToLoginFragment()
                findNavController().navigate(action)
            }
        }
    
    3.但是这样跳转很不美观,所以我们可以添加一些动画效果。slide_in如下图所示,其他的都差不多。一共有四个动画。
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500">
        <translate
            android:fromXDelta="100%"
            android:toXDelta="0%"
            />
    </set>
    
    4.然后在nav_graph里面把这几个动画添加进去。
    添加动画
    5.添加一个WelcomeFragment和xml文件,随便布局一下,然后再和WelcomeFragment关联一下。
    fragmenr_welcome.xml
    6.把它添加进nav_graph,然后和loginFragment关联一下。
    关联结果
    7.关联完了之后,再把之前的动画添加进去。直接到代码中,把之前的哪那些动画代码copy进去即可。
               app:enterAnim="@anim/slide_in"
                app:exitAnim="@anim/slide_out"
                app:popEnterAnim="@anim/pop_in"
                app:popExitAnim="@anim/pop_out" 
    
    8.然后在LoginFragment里面实现按钮的点击事件。
    override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            button_confirm.setOnClickListener {
                val action = LoginFragmentDirections.actionLoginFragmentToWelcomeFragment()
                findNavController().navigate(action)
            }
        }
    
    9.如果想要传递数据怎么办呢,首先在nav_graph里面选中welcomeFragment,然后点击Arguments里面的+号
    添加接收的数据
    • 然后在LoginFragment里面把数据传递进去
    val action = 
    LoginFragmentDirections.actionLoginFragmentToWelcomeFragment(name_edit_view.text.toString())
    
    10.在WelcomeFragment里面获取传递过来的参数值。
    class WelcomeFragment :Fragment(R.layout.fragment_welcome){
        val args:WelcomeFragmentArgs by NavArgs()
    
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            name_text_view.text=args.Username
        }
    
    }
    
    • 或者直接使用arguments
     name_text_view.text = arguments?.getString("username")
    
    • 这样点击之后,值就会传递过去。Jack是我在前面一个框内写入的内容。
    Jack传过去了
    11.如果我们想让输入的内容也变为toolBar的标题的话,那么就在nav_graph中属于welcomeFragment的代码里面修改一下。这样就会显示为标题了。
    android:label="{username}"
    
    • 效果如下图所示:
    效果图
    12.将welcome界面和home界面关联起来,当点击back按钮的时候会立刻跳转到Home页面。在WelcomeFragment里面,实现按钮的点击事件。
     button_back.setOnClickListener {
                findNavController().navigate(
                    WelcomeFragmentDirections.actionWelcomeFragmentToHomeFragment()
                )
            }
    
    13.如果用户成功登录了的话,那么按底下的返回就不会跳到上一个页面,而是跳到主界面。我们就在nav_graph里面,给loginFragment和WelcomeFragment之间那根线,设置一下它的popBehavior
    设置
    • 这样在最后一次登录了的时候,点击下方的回退,back按钮,就会直接到home主页面了。

    相关文章

      网友评论

          本文标题:Android开发(29)——Fragment和Navigati

          本文链接:https://www.haomeiwen.com/subject/hvlsdltx.html