一 简介
- Fragment是一种可以嵌入在Activity当中的UI片段,它能让程序更加合理和充分地利用大屏幕空间,在平板上应用广泛。
二 静态添加Fragment
- xml leftFragme
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"
android:textAllCaps="false"/>
</LinearLayout>
- xml rightFragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00ff00">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is right fragment"
android:textSize="25sp"/>
</LinearLayout>
- 新建两个fragment类继承Fragment
class LeftFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.left_fragment, container,false)
return view
}
}
class RightFragment: Fragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.right_fragment, container, false)
return view
}
}
- 在MainActivity的布局中引入
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal">
<fragment
android:id="@+id/leftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.kotlin_fragment.staticfragment.LeftFragment"/>
<fragment
android:id="@+id/rightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.kotlin_fragment.staticfragment.RightFragment"/>
</LinearLayout>
-
效果图
staticfragment.png
三 动态添加Fragment
步骤
- 创建待添加的Fragment实例
- 获取FragmentManager,在Activity中可以直接调用
getSupportFragmentManager()
方法获取 - 开启一个事务,通过调用
fragmentManager
的beginTransaction()
方法获取一个容器 - 向容器内添加或者替换Fragment,一般使用
replace()
方法实现,需要传入容器的id(Fragment的父布局)和待添加的Fragment
实例 - 提交事务,调用
commit()
方法来完成
class MainActivity : AppCompatActivity() {
private lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
replaceFragment(RightFragment())
setListener()
}
private fun setListener() {
button.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
}
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.rightLayout, fragment)
/**
*addToBackStack: 用于将事务添加到返回栈当中
* 它接收一个名字用于描述返回栈的状态,一般传入null就ok
*/
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
}
四 Fragment和Activity之间的交互
- 为了方便Fragment和Activity之间进行交互,
FragmentManager
提供了一个类似于findViewById()
的方法,专门用于从布局文件中获取Fragment
的实例- 代码如下所示:
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrgament) as LeftFragment
- 调用
FragmentManager
的findFragmentById()
方法,可以在Activity中得到相应Fragment的实例,然后就能轻松地调用Fragment里的方法了
- 那么在Fragment中又该如何调用Activity里的方法呢?
在每个
Fragment
中都可以通过调用getActivity()
方法来得到和当前Fragment
相关联的Activity实例,例如:if(activity != null){ val mainActivity = activity as MainActivity }
这里由于
getActivity()
方法有可能返回null,因此我们需要先进行一个判空处理,有了Activity的实例,就能调Activity里面的方法了;另外当Fragment
中需要使用Context
对象时,也可以使用getActivity()
方法,因为获取到的Activity本身就是一个Context对象。
- 那么不同的Fragment之间怎么通信呢?
思路非常简单:首先在一个
Fragment
中可以得到与它相关联的Activity
,然后再通过这个Activity去获取另外一个Fragment
的实例。
五 Fragment的生命周期
- Activity有的生命周期Fragment都有,附加的如下:
- onAttach(): 当Fragment和Activity建立关联时回调
- onCreateView(): 为Fragment创建视图(加载布局)时调用
- onActivityCreate(): 确保与Fragment相关联的Activity已经创建完毕时调用
- onDestroyView(): 当与Fragment关联的视图被移除时调用
- onDetach(): 当Fragment和Activity解除关联时调用
-
Fragment生命周期图如下:
fragment.jpg
六 动态加载布局的技巧
- 让程序能够根据设备的分辨率或屏幕大小,在运行时决定加载哪个布局
- 比如如果是手机,则采用单页模式,平板则采用双页模式
- 需要借助限定符来实现
- 修改layout下面activity_main.xml文件
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal">
<fragment
android:id="@+id/leftFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.kotlin_fragment.staticfragment.LeftFragment"/>
</LinearLayout>
- 在res目录下新建layout-large文件夹,在这个文件夹下也新建activity_main.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal">
<fragment
android:id="@+id/leftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.kotlin_fragment.staticfragment.LeftFragment"/>
<fragment
android:id="@+id/rightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:name="com.example.kotlin_fragment.staticfragment.RightFragment"/>
</LinearLayout>
可以看到,layout/activity_main布局只包含了一个Fragment,即单页模式,而layout-large/activity_main布局包含了两个Fragment,即双页模式。其中,large就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large文件夹下的布局,小屏幕的设备则还是会加载layout文件夹下的布局。
使用最小宽度限定符
有时候我们希望可以更加灵活地为不同设备加载布局,不管它们是不是被系统认定为large,这时就可以使用最小宽度限定符。
- 最新宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于等于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。
- 在res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity_main.xml布局。
- 这就意味着,当程序运行在屏幕宽度大于等于600dp的设备上时,会加载layout-sw600dp/activity_main布局,小于则加载默认的layout/activity_main。
七 实践:一个简易版的新闻应用
- 标题fragment
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/newsTitleRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
单双页模式下 显示的内容不同 双页只显示标题点击跳转到内容
class NewsTitleFragment: Fragment(){
private var isTwoPane = false
private lateinit var mNewsTitleRecyclerView: RecyclerView
private lateinit var mNewsAdapter: NewsAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.news_title_frag,container,false)
mNewsTitleRecyclerView = view.findViewById(R.id.newsTitleRecyclerView)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//如果 是双页模式 加载的另一个布局 那个布局是有newsContentLayout这个控件的
isTwoPane = activity?.findViewById<View>(R.id.newsContentLayout) != null
val layoutManager = LinearLayoutManager(activity)
mNewsTitleRecyclerView.layoutManager = layoutManager
mNewsAdapter = NewsAdapter(getNews())
mNewsTitleRecyclerView.adapter = mNewsAdapter
}
private fun getNews(): List<News> {
val newsList = ArrayList<News>()
for (i in 1..50){
val news = News("this is news title $i",getRandomLengthString("this is news content $i. "))
newsList.add(news)
}
return newsList
}
private fun getRandomLengthString(s: String): String {
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(s)
}
return builder.toString()
}
inner class NewsAdapter(val mNewList: List<News>):
RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val newsTitle: TextView = itemView.findViewById(R.id.newsTitle)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent,false)
val holder = ViewHolder(itemView)
holder.itemView.setOnClickListener {
val news = mNewList[holder.adapterPosition]
if (isTwoPane){
//如果是双页模式 则刷新NewsContentFragment中的内容
if (activity != null){
val fragmentActivity = activity as FragmentBestPractice
val fragment = fragmentActivity.supportFragmentManager.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
fragment.refresh(news.title, news.content)
}else{
//如果是单页模式 则直接启动NewsContentActivity
NewsContentActivity.actionStart(parent.context, news.title,news.content)
}
}
}
return holder
}
override fun getItemCount() = mNewList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val news = mNewList[position]
holder.newsTitle.text = news.title
}
}
}
- 内容fragment
xml:
<?xml version="1.0" encoding="utf-8"?>
<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/newsTitle"
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_alignParentStart="true"
android:background="#000"/>
</RelativeLayout>
class NewsContentFragment : Fragment(){
private lateinit var mContentLayout: LinearLayout
private lateinit var mNewsTitle: TextView
private lateinit var mNewsContent: TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.news_content_frag,container,false)
initView(view)
return view
}
private fun initView(view: View) {
mContentLayout = view.findViewById(R.id.contentLayout)
mNewsTitle = view.findViewById(R.id.newsTitle)
mNewsContent = view.findViewById(R.id.newsContent)
}
fun refresh(title: String, content: String){
mContentLayout.visibility = View.VISIBLE
mNewsTitle.text = title
mNewsContent.text = content
}
}
双页模式下跳转过来的内容activity
class NewsContentActivity : AppCompatActivity(){
companion object{
fun actionStart(context: Context, title: String, content: String){
val intent = Intent(context, NewsContentActivity::class.java).apply {
//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)
initView();
}
private fun initView() {
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)
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/newsContentFrag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.kotlin_fragment.practice.NewsContentFragment"/>
</LinearLayout>
- 适配的主界面
- 默认的layout
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.kotlin_fragment.practice.NewsTitleFragment"/>
</FrameLayout>
- large的layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="@+id/newsTitleFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.kotlin_fragment.practice.NewsTitleFragment"/>
<FrameLayout
android:id="@+id/newsContentLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<fragment
android:id="@+id/newsContentFrag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.kotlin_fragment.practice.NewsContentFragment"/>
</FrameLayout>
</LinearLayout>
- 主activity
class FragmentBestPractice : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news_project)
}
}
-
效果图
fragment2.png