本节内容
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.Fragment
和Activity
之间的关系:
-
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包,然后添加动画的资源文件。
目录
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500">
<translate android:fromXDelta="100%" android:toXDelta="0%"/>
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500">
<translate android:fromXDelta="0%" android:toXDelta="-100%"/>
</set>
.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.在LoginFragment
的onActivityCreated
方法里面先把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()
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图标
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
}
}
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主页面了。
网友评论