Fragment学习

一、什么是Fragment

Fragment是一种可以嵌入在Activity中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用地非常广泛。
它与Activity很像,包含布局,有自己的生命周期,可以理解成一个迷你型的Activity,虽然它有可能和普通的Activity一样大。

举个例子,假如我们现在在开发一个新闻应用,其中一个界面使用RecyclerView展示了一组新闻的标题,当点击其中一个标题时,就打开另一个界面显示新闻的详细信息。平板的空间较大,所以我们最好的设计方案是将新闻标题列表界面和新闻详细内容界面放在两个Fragment中,然后在同一个Activity中引入这两个Fragment,左侧显示新闻标题,右侧显示新闻内容,点击左侧标题切换右侧的内容。

二、Fragment的使用方式

1、Fragment的简单用法

首先写一个样例,在一个Activity中添加两个Fragment,并让这两个Fragment平分Activity空间:

① 左侧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/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="一个按钮"/>
    
</LinearLayout>

其中只放置了一个按钮,且让它居中显示

②右侧Fragment,right_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#87CEFA"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="24sp"
        android:text="这是右边的Fragment" />

</LinearLayout>

将背景改为了蓝色,并放置了一个TextView用于显示一段文本。

接着新建一个LeftFragment类和一个RightFragment类,并让它们继承自Fragment(这里使用AndroidX库中的androidx.fragment.app.Fragment,它可以让Fragment的特性在所有Android系统版本中保持一致):

class LeftFragment : Fragment(){
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.left_fragment,container,false)
    }
}
class RightFragment : Fragment(){
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.right_fragment,container,false)
    }
}

这两个类仅仅是重写了onCreateView()方法,然后通过LayoutInflater的inflate()方法将刚才定义的left_fragment和right_fragment布局动态地加载进来。

下面修改activity_main.xml中的代码:

<?xml version="1.0" encoding="utf-8"?>
<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>

这里使用了<fragment>标签在布局中添加Fragment,其中使用android:name来显式地声明要添加的Fragment类名,需要加包名。

2、动态添加Fragment

Fragment的真正强大之处在于它可以在程序运行时动态地添加到Activity中。

将上文代码完善,新建一个another_right_fragment.xml:

<?xml version="1.0" encoding="utf-8"?>
<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="这是另一个右侧Fragment"
        />

</LinearLayout>

背景设为黄色,放置了一个TextView用于显示一段文本。

然后新建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)
    }
}

即在onCreateView()方法中加载another_right_fragment.xml布局。

下面看如何将它动态地添加到Activity中。
修改activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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" />

</LinearLayout>

现在将右侧Fragment替换成了一个FrameLayout,下面在代码中向FrameLayout里添加内容,从而实现动态地添加Fragment的功能,修改MainActivity:


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        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()
    }
}

上述代码中给左侧Fragment注册了一个点击事件,然后调用replaceFragment()方法动态地添加了RightFragment。当点击左侧按钮时,又会调用replaceFragment()方法将右侧的Fragment换成了AnotherRightFragment。

结合replaceFragment()方法中的代码,动态添加Fragment主要分五步

  • 创建待添加的Fragment实例
  • 获取FragmentManager:在Activity中可以直接调用getSupportFragmentManager()方法获取
  • 开启一个事务:通过调用beginTransaction()方法开启
  • 向容器中添加或替换Fragment:一般使用replace()方法实现,传入容器id和待添加的Fragment实例
  • 提交事务:调用commit()方法完成。

3、在Fragment中实现返回栈

上文代码中通过点击按钮添加了一个人Fragment,此时按一下Back键就会直接退出。
可以利用FragmentTransaction提供的addToBackStack()方法将一个事务添加到返回栈。
修改ManActivity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        replaceFragment(RightFragment())
        button.setOnClickListener {
            replaceFragment(AnotherRightFragment())
        }

    }

    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即可。

4、Fragment和Activity之间的交互

Fragment和Activity是各自存在于一个独立的类中的,没有明显的交互方式,为是西安两者之间的交互,FragmentManager提供了了一个类似findViewById()的方法,专门用于从布局文件中获取Fragment的实例,代码如下:

val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment

在Fragment中调用Activity可以通过getActivity()方法来得到当前Fragment相关联的Activity实例:

if(activity!=null){
	val mainActivity = activity as MainActivity
}

在Fragment中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的Activity本身就是一个Context对象。

Fragment之间的交互

基本思路:在一个Fragment中得到与它相关联的Activity,再通过这个Activity去获取另一个Fragment实例。

三、Fragment的生命周期

Fragment的状态和回调

Fragment在其声明周期内存在的状态:
  • 运行状态: 当一个Fragment所关联的Activity正处于运行状态时,该Fragment也处于运行状态。
  • 暂停状态: 当一个Activity进入暂停状态时,与他相关联的Fragment就会进入暂停状态。
  • 停止状态: 当一个Activity进入停止状态时,与他相关联的Fragment就会进入停止状态。进入停止状态的Fragment对用户完全不可见,可能会被系统回收。
  • 销毁状态: 当Activity被销毁时,与他相关联的Frragment就会进入销毁状态;或者通过调用FragmentTransactionremove()replace()方法将Fragment从Activity中移除,但在事务提交之前没有调用addToBackStack()方法,这时的Fragment也会进入销毁状态。
Fragment的回调方法

在Fragment中,Activity有的回调方法Fragment基本都有,但Fragment还提供了一些附加的回调方法:

  • onAttach():当Fragment和Activity建立关联时调用。
  • onCreateView():为Fragment创建试图(加载布局)时调用。
  • onActivityCreated():确保与Fragment相关联的Activity已经创建完毕时调用。
  • onDestroyView():当与Fragment关联的试图被移除时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

四、动态加载布局的技巧

如果程序可以根据设备的分辨率或屏幕大小,在运行的时候决定加载哪个布局,那可以发挥的空间就更多了,事实上也可以是实现这个功能。

1、使用限定符

很多平板应用采用的是双页模式(程序会在左侧面板上显示一个包含子项的列表,在右侧面板显示内容),因为平板的屏幕足够大,完全可以同时显示两页的内容,但手机的屏幕就只能显示一页,需要分开显示。

可以借助限定符(qualifier)来实现。

举个例子:
首先修改activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<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>

现在只存在一个左侧Fragment,充满整个父布局,下面在res目录下新建layout-large文件夹,同样新建一个布局,也叫activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<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>

large是一个限定符,那些被认定为是large的设备就会自动加载layout-large下的布局,小屏幕的设备还是会加载layout文件夹下的布局。
之后将MainActivity中的replaceFragment()方法里的代码注释掉,重新在平板模拟器和手机模拟器上启动,这样就实现了在程序运行时动态加载布局的功能。

Android常见限定符
屏幕特征限定符描述
大小small提供给小屏幕设备的资源
大小normal提供给中等屏幕设备的资源
大小large提供给大屏幕设备的资源
大小xlarge提供给超大屏幕设备的资源
分辨率ldpi提供给低分辨率设备的资源(120dpi以下)
分辨率mdpi提供给中等分辨率设备的资源(120dpi~160dpi)
分辨率hdpi提供给高分辨率设备的资源(160dpi~240dpi)
分辨率xhdpi提供给超高分辨率设备的资源(240dpi~320dpi)
分辨率xxhdpi提供给超超高分辨率设备的资源(320dpi~480dpi)
方向land提供给横屏设备的资源
方向port提供给竖屏设备的资源

2、使用最小宽度限定符

最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值就加载一个布局,小于这个值就加载另一个布局。

在res目录下新建layout-sw600dp文件夹,然后在其中新建activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<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>

这就意味着当程序运行在屏幕宽度大于等于600dp的设备上时,就会加载/layout-sw600dp/activity_main.xml,小于就加载/layout/activity_main.xml布局。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅梦曾倾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值