5.1 Fragment是什么
①一种可以嵌入在Activity当中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,因而在平板
上应用得非常广泛。
②Fragment虽然是个全新的概念,但它和Activity存在众多相似之处,同样都能包含布局,同样都有自己的生命周期。
③可以将Fragment理解成一个迷你型的Activity,虽然这个迷你型的Activity可能和普通的Activity是一样大的。
比如在手机上设置一个新闻页面:
由于手机屏幕有限,所以只能通过新闻的标题栏进入新闻的主要内容。
如果是在平板上面,由于平板空间较大,所以上面两个页面也可以放置在一个页面上:
为了避免提高代码复用,这就有了Fragment
,若果检测到设备屏幕宽读较小就使用上面的布局,如果屏幕宽度较大就使用下面的。
5.2 Fragment的使用方式
5.2.1 基础使用
首先执行下面两个步骤:
①首先创建一个平板模拟器,这次创建一个Pixel C平板模拟器,创建完成后启动模拟器,效果如图所示。
②接着新建一个FragmentTest项目
接下来的步骤如下:
①新建一个左侧Fragment
的布局left_fragment.xml
,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"
/>
</LinearLayout>
② 新建右侧Fragment
的布局right_fragment.xml
,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:text="This is right fragment"
/>
</LinearLayout>
③新建一个`LeftFragment`类,让它**继承**自`Fragment`。
注:可能会有两个不同包下的Fragment:
- 系统内置的
android.app.Fragment
- AndroidX库中的
androidx.fragment.app.Fragment
。请一定使用AndroidX库中Fragment,因为它可以让Fragment的特性在所有Android系统版本中保持一致,而系统内置的Fragment在Android 9.0版本中已被废弃。
class LeftFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.left_fragment, container, false)
}
}
④用同样的方法再新建一个RightFragment,代码如下所示:
class RightFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.right_fragment, container, false)
}
}
这里再解释一下其中的参数的意义:
inflater: LayoutInflater
:布局加载参数container: ViewGroup?
:根视图(也就是这个Fragment需要被放置在哪个视图当中)savedInstanceState: Bundle?
:用于传递数据的参数inflater.inflate
中的三个参数分别指的是:- 布局的资源id(R.layout.right_fragment)
- 填充的根视图(container)
- 是否将载入的视图绑定到根视图中(false)
⑤修改activity_main.xml
,在布局中引入Fragment
。如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
这里只要注意name属性即可。
最后就可以运行了:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1648796610332-46b39043-3883-45da-b404-b47bae6c81a7.png#clientId=u2131b7a2-d5c1-4&from=paste&height=326&id=u870395e4&originHeight=562&originWidth=861&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22195&status=done&style=none&taskId=ua1e2c0a7-d030-4436-941e-06b7dac027d&title=&width=500)
5.2.2 动态添加Fragment
Fragment真正的强大之处在于,它可以在程序运行时动态地添加到**Activity**
当中。根据具体情况来动态地添加Fragment,你就可以将程序界面定制得更加多样化。
后面的步骤如下:
①在上一节基础上继续完善,新建another_right_fragment.xml
,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#ffff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:text="This is another right fragment"/>
</LinearLayout>
②新建`AnotherRightFragment`作为另一个**右侧**`Fragment`,代码如下所示:
class AnotherRightFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.another_right_fragment, container, false)
}
}
③修改`activity_main.xml`:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
</FrameLayout>
</LinearLayout>
现在将右侧
Fragment
替换成了一个FrameLayout
,该布局是Android中最简单的一种布局,所有的控件默认都会摆放在布局的左上角。
④向`FrameLayout`里**添加内容**,实现动态添加`Fragment`的功能。修改`MainActivity`中的代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取在左侧Fragment的按钮
val button:Button = findViewById(R.id.button)
// 点击按钮 触发事件切换右侧的Fragment
button.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
replaceFragment(RightFragment())
}
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout, fragment)
transaction.commit()
}
}
这里讲一下`replaceFragment`,动态添加`Fragment`的步骤有五步:<br /> ①创建待添加`Fragment`的实例。
这里就是传入的Fragment参数
②获取FragmentManager
,在Activity中可以直接调用getSupportFragmentManager()方法
获取。
这是是Kotlin的语法糖,
getSupportFragmentManager()方法
可以简写成supportFragmentManager
,也就是Fragment管理器。
③开启一个事务,通过调用beginTransaction()
方法开启。
④ 向容器内添加或替换Fragment,一般使用replace()
方法实现,需要传入容器的id和待添加的Fragment实例。
前面一个就是当前的布局id:
R.id.rightLayout
,后面是需要替换的布局:fragment
⑤提交事务,调用commit()
方法来完成。
这里补充一下什么是事务:
事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。
简单一点的理解就是,在事务中执行的语句,只能全部完成,如果有一项有错误,那么整个就会回滚到之前的状态。
更多内容可以参照:
什么是事务?事务的四个特性以及事务的隔离级别 - Kevin.ZhangCG - 博客园
5.2.3 实现返回栈
很简单,只需要添加一条代码即可:
class MainActivity : AppCompatActivity() {
…....
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout, fragment)
// 实现返回栈
transaction.addToBackStack(null)
transaction.commit()
}
}
在事务提交之前调用了FragmentTransaction的addToBackStack()方法,可以接收一个名字用于描述返回栈的状态,一般传入null即可。 这个时候我们按下Back键可以回到上一个Fragment。
5.2.4 Fragment和Activity之间的交互
为方便Fragment和Activity之间进行交互,FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取Fragment的实例,代码如下所示:
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment
用FragmentManager的findFragmentById()方法,可以在Activity中得到相应Fragment的实例,就能轻松调用Fragment里的方法。
在Fragment中怎样调用Activity里的方法?在每个Fragment中都可以通过调用getActivity()
方法来得到和当前Fragment相关联的Activity实例,代码如下所示:
if (activity != null) {
val mainActivity = activity as MainActivity
}
5.3 Fragment的生命周期(了解即可)
5.3.1 Fragment的状态和回调
①运行状态
当一个Fragment所关联的Activity正处于运行状态时,该Fragment也处于运行状态。
②暂停状态
当一个Activity进入暂停状态时(由于另一个未占满屏幕的Activity被添加到了栈顶),与它相关联的Fragment就会进入暂停状态。
③停止状态
当一个Activity进入停止状态时,与它相关联的Fragment就会进入停止状态,或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前调用了addToBackStack()方法,这时的Fragment也会进入停止状态。总的来说,进入停止状态的Fragment对用户来说是完全不可见的,有可能会被系统回收。
④销毁状态
Fragment总是依附于Activity而存在,因此当Activity被销毁时,与它相关联的Fragment就会进入销毁状态。或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前并没有调用addToBackStack()方法,这时的Fragment也会进入销毁状态。
5.3.2 体验生命周期
class RightFragment : Fragment() {
companion object {
const val TAG = "RightFragment"
}
override fun onAttach(context: Context) {
super.onAttach(context)
Log.d(TAG, "onAttach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
Log.d(TAG, "onCreateView")
return inflater.inflate(R.layout.right_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Log.d(TAG, "onActivityCreated")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop")
}
override fun onDestroyView() {
super.onDestroyView()
Log.d(TAG, "onDestroyView")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG,"onDestroy")
}
override fun onDetach() {
super.onDetach()
Log.d(TAG,"onDetach")
}
}
5.4 动态加载布局的技巧
5.4.1 使用限定符
基本使用
如果你经常使用平板,应该会发现很多平板应用采用的是双页模式(程序会在左侧的面板上显示一个包含子项的列表,在右侧的面板上显示内容),因为平板的屏幕足够大,完全可以同时显示两页的内容,但手机的屏幕就只能显示一页的内容,因此两个页面需要分开显示。在运行时判断程序应该使用双页模式还是单页模式,需要借助限定符(qualifier)来实现。
① 修改FragmentTest项目中的activity_main.xml文件,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
② 接着在res目录下新建`layout-large`文件夹(Project视图),在这个文件夹下新建一个布局,也叫作`activity_main.xml`,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
可以看到,layout/activity_main布局只包含了一个**Fragment**
,即单页模式,而layout-large/activity_main
布局包含了两个Fragment,即双页模式。
其中,large
就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large文件夹下的布局,小屏幕的设备则还是会加载layout文件夹下的布局。
③ 最后记得先注释掉以下代码:
最后会发现在手机和在平板上的显示如下:
常见限定符
屏幕特征 | 限定符 | 描述 |
---|---|---|
大小 | small | 提供给小屏幕设备的资源· |
normall | 提供给中等屏幕设备的资源 | |
large | 提供给大屏幕设备的资源 | |
xlarge | 提供给超大屏幕设备的资源 | |
分辨率 | ldpi | 提供给低分辨率设备的资源(120dpi 以下) |
mdpi | 提供给中等分辨率设备的资源( 120dpi到160dpi) | |
hdpi | 提供给高分辨率设备的资源( 160dpi到240dpi) | |
xhdpi | 提供给超高分辨率设备的资源(240dpi到 320dpi) | |
方向 | land | 提供给横屏设备的资源 |
port | 提供给竖屏设备的资源 |
5.4.2 使用最小宽度限定符
有时候我们希望可以更加灵活地为不同设备加载布局,不管它们是不是被系统认定为large,这时就可以使用最小宽度限定符(smallest-width qualifier)。
最小宽度限定符允许对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。
举个例子:
在res目录下新建layout-sw600dp
文件夹,然后在这个文件夹下新建activity_main.xml布局,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
这意味着,当程序运行在屏幕宽度大于等于600 dp
的设备上时,会加载layout-sw600dp/activity_main
布局,当程序运行在屏幕宽度小于600 dp
的设备上时,则仍然加载默认的layout/activity_main
布局。
5.5 新闻实例分析
先看一下最终效果:
在平板上,点击左侧的title
,右侧能够同步刷新相关的内容。
在手机上,点击title
,能够跳转到相应的页面。
需求分析
根据上面的效果图以及需求,可以得出,我们需要两个fragment
,一个是新闻标题栏的fragment
,一个是具体内容的fragment
,我们可以分别取名为:NewsTitleFragment
和NewsContentFragment
。
- 屏幕比较小的时候,
MainActivity
当中应该只有一个NewsTitleFragment
。- 点击每一个
title
,会跳转到相应的新闻内容当中去,所以还需要一个Activity
存放新闻内容,我们将这个Activity
取名为:**NewContentActivity**
。
- 点击每一个
- 屏幕比较大的时候,
MainActivity
当中应该同时存在一个NewsTitleFragment
和一个NewsContentFragment
。- 点击每一个
title
,右侧内容区域就会刷新相应的内容。
- 点击每一个
接下来分析一下布局:
NewsTitleFragment
需要一个布局文件,这里我们取名为:news_title_frag
- 由于这是个列表展示,所以这里需要使用到我们上一章学到的
RecyclerView
,那就必需创建一个子项的布局:news_item
。
- 由于这是个列表展示,所以这里需要使用到我们上一章学到的
NewsContentFragment
也需要一个布局文件:news_content_frag
- 剩下两个Activity也需要一个布局文件,这里是自动生成的不用管:
activity_main
和activity_news_contents
.- 需要注意的是,
MainActivity
需要适配两个宽度的设备,layout
下的是默认布局,所以还需要新建一个layout-sw600dp
目录,这个目录下再创建一个activity_main
,用于适配平板。
- 需要注意的是,
最后是根据上节课中的RecyclerView
需要创建相应的News
类,最后我们需要的布局和类如下:
这里会发现没有建立对应的适配器,这个后面会提到,现在需要建立的布局和类就这么多。
设计阶段
设计基础模型
基础的News
模型很简单,只需要有新闻的标题(title)和新闻的内容(content)即可:
package com.example.fragmentbestpractice.model;
class News(val title: String, val content: String) {
}
接下来给单个子项(news_item)设置布局:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/newsTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textSize="18sp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:paddingBottom="15dp" />
注意这里只需要一个
TextView
即可,不要添加任何的Layout
设置新闻内容区域
首先设置布局news_content_frag
:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible" >
<TextView
android:id="@+id/newsTitleAboveContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:textSize="20sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000" />
<TextView
android:id="@+id/newsContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="15dp"
android:textSize="18sp" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="#000" />
</RelativeLayout>
这里的
View
控件就是分割线的作用。
比较简单,就不细讲了。
接下来设计NewsContentFragment
:
package com.example.fragmentbestpractice.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.fragmentbestpractice.R
class NewsContentFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.news_content_frag, container, false)
}
fun refresh(title: String, content: String) {
val contentLayout: LinearLayout? = activity?.findViewById(R.id.contentLayout)
contentLayout?.visibility = View.VISIBLE
// 获取新闻的标题,这个标题是在后面内容布局里面的标题
val newsTitle: TextView? = activity?.findViewById(R.id.newsTitleAboveContent)
// 获取新闻的内容,这个标题是在后面内容布局里面的内容
val newsContent:TextView? = activity?.findViewById(R.id.newsContent)
newsTitle?.text = title // 刷新新闻的标题
newsContent?.text = content // 刷新新闻的内容
}
}
这里会有部分地方关联其他类或者布局,会有报错,先不用管。
主要讲一下这里的刷新方法:
刷新方法传入了两个参数:title: String
,content: String
,就是新闻的标题和内容。
刷新方法中后面四局代码很好理解,注释里面写了,这里不再赘述。
主要是讲一下这里前面两行:
val contentLayout: LinearLayout? = activity?.findViewById(R.id.contentLayout)
contentLayout?.visibility = View.VISIBLE
第一句的作用是在fragment
中调用activity
的方法,通过id找寻到id为contentLayout
的控件,这个控件在后面会提到,只在屏幕较大的时候使用了这个控件,也就是说只有屏幕大的时候,才有contentLayout
,第二句的意思就是使这个控件可见。
为什么加?
,是因为屏幕小的时候就不会有这个控件,所以这个变量可能是null,所以加上?
防止了空指针异常。
设计新闻标题栏
首先设置布局news_title_frag
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/newsTitleRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
设计`NewsTitleFragment`:
代码比较长,点击左上角三角形可以收起代码块。
package com.example.fragmentbestpractice.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.fragmentbestpractice.Activity.NewsContentActivity
import com.example.fragmentbestpractice.R
import com.example.fragmentbestpractice.model.News
class NewsTitleFragment : Fragment() {
private var isTwoPane = false // 判断是不是双页模式,默认不是
// 基础创建视图
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.news_title_frag, container, false)
}
// 当Activity创建的时候,执行的代码
override fun onActivityCreated(savedInstanceState: Bundle?) {
// 保存数据
super.onActivityCreated(savedInstanceState)
// 判断有没有id是newsContentLayout的空间,如果有说明是在平板上,说明是双页模式,后面的逻辑语句就成真,这个时候isTwoPane = True,反之就是默认不是
isTwoPane = activity?.findViewById<View>(R.id.newsContentLayout) != null
// 获取当前Activity下的布局管理器
val layoutManager = LinearLayoutManager(activity)
// 根据id获取RecyclerView的实例,activity可能是空的,因为可能没有activity使用这个fragment
val newsTitleRecyclerView: RecyclerView? = activity?.findViewById(R.id.newsTitleRecyclerView)
// 下面就是布局管理器和适配器的配置
newsTitleRecyclerView?.layoutManager = layoutManager
// 适配器接受一个数组
val adapter = NewsAdapter(getNews())
newsTitleRecyclerView?.adapter = adapter
}
// 生成50个News实例,组装在一个List数组中返回
private fun getNews(): List<News> {
val newsList = ArrayList<News>()
for (i in 1..50) {
// 随机获取不同长度的内容,这个内容重复1-20次的标题
val news = News("This is news title $i", getRandomLengthString("This is news content $i. "))
newsList.add(news)
}
return newsList
}
// 随机获取不同长度的内容
private fun getRandomLengthString(str: String): String {
// 随机重复1-20次str
val n = (1..20).random()
val builder = StringBuilder()
repeat(n) {
builder.append(str)
}
return builder.toString()
}
// 这里把适配器定义为内部类了,因为需要使用到Fragment中的参数
// 一个比较复杂的继承,没什么好讲的,我也不想去查官方文档了
inner class NewsAdapter(val newsList: List<News>) : RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
// 这里和上一章内容一样
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val newsTitle: TextView = view.findViewById(R.id.newsTitle)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent, false)
val holder = ViewHolder(view)
// 给每一个子项设置点击事件
holder.itemView.setOnClickListener {
// 获取新闻列表中对应位置的News实例
val news = newsList[holder.bindingAdapterPosition]
// 如果是双页模式,则刷新NewsContentFragment中的内容
if (isTwoPane) {
获取内容面板的实例
val fragment = fragmentManager?.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
// 刷新的内容是获取的News实例的标题和内容
fragment?.refresh(news.title, news.content)
}
// 如果是单页模式,则直接启动NewsContentActivity
else {
// 这个启动方法后面会讲,这里只要知道是启动新闻内容页面即可,后面可以回过头了再看看
NewsContentActivity.actionStart(parent.context, news.title, news.content)
}
}
return holder
}
// 下面两个也和上一章内容一样
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val news = newsList[position]
holder.newsTitle.text = news.title
}
override fun getItemCount() = newsList.size
}
}
解析都在注释里面了,这里就不讲了。 这里放一下上一章有关适配器的链接:
设计单独的新闻内容页
首先还是布局:activity_news_content
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/newsContentFrag"
android:name="com.example.fragmentbestpractice.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
内容很简单,就是放了一个fragment
,还是比较好理解的。
看一下Activity
:
package com.example.fragmentbestpractice.Activity
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.fragmentbestpractice.R
import com.example.fragmentbestpractice.fragment.NewsContentFragment
class NewsContentActivity : AppCompatActivity() {
companion object {
fun actionStart(context: Context, title: String, content: String) {
val intent = Intent(context, NewsContentActivity::class.java).apply {
putExtra("news_title", title)
putExtra("news_content", content)
}
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news_content)
val title = intent.getStringExtra("news_title") // 获取传入的新闻标题
val content = intent.getStringExtra("news_content") // 获取传入的新闻内容
if (title != null && content != null) {
val fragment = supportFragmentManager.findFragmentById(R.id.newsContentFrag)
as NewsContentFragment
fragment.refresh(title, content) //刷新NewsContentFragment界面
}
}
}
companion object
就当做静态使用就好了,详情参照下面的文章,不理解也没关系。//
[kotlin]kotlin中的伴生对象(companion object)到底是个什么东西?_黑森林中的小木屋的博客-CSDN博客_companion object
讲一下里面的启动函数:
fun actionStart(context: Context, title: String, content: String) {
val intent = Intent(context, NewsContentActivity::class.java).apply {
putExtra("news_title", title)
putExtra("news_content", content)
}
context.startActivity(intent)
}
后面两个参数很好理解,第一个参数指的是上下文,实如果说是从`MainActivity`触发了这个时间,那么这个启动函数会从`MainActivity`跳转到`NewsContentActivity`。
这里的context指代的就是
Activity
,就是可以从其他Activity
转到NewsContentActivity
。
设计MainActivity
这个就很简单了,直接看代码:
package com.example.fragmentbestpractice.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.fragmentbestpractice.R
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
设计布局:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/newsTitleLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/newsTitleFrag"
android:name="com.example.fragmentbestpractice.fragment.NewsTitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/newsTitleFrag"
android:name="com.example.fragmentbestpractice.fragment.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/newsContentLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" >
<fragment
android:id="@+id/newsContentFrag"
android:name="com.example.fragmentbestpractice.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
注意这里的
newsContentLayout
,就是上面一直提到的新闻内容面板。