提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。
文章目录
一、Fragment的使用
Fragment是一种可以嵌入在Activity中的UI片段,在平板上应用非常广泛。我们可以在一个Activity中引入多个Fragment来显示更多的内容,因此Fragment可以很好的在大屏幕Android设备中发挥作用。
1.1 Fragment的基础使用
我们创建一个FragmentTest项目,并新建一个左侧Fragment布局文件left_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">
<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:
<?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:background="#00ff00"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:text="This is right fragment!"
android:textSize="24sp" />
</LinearLayout>
在使用Fragment时,除了创建Fragment布局文件。我们还需要创建相应的类文件来表示该Fragment。这是因为Fragment本身是一个独立的组件,它可以在Activity中运行,并且有自己的生命周期和UI。:
//注意是androidx中的fragment
import androidx.fragment.app.Fragment
· · ·
class LeftFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.left_fragment,container,false)
}
}
新建一个RightFragment类,RightFragment类的代码和LeftFragment类基本一致,只是引用的fragment布局不同。接下来修改activity_main.xml中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
//LeftFragment
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
//RightFragment
<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平分,左边的LeftFragment中显示了一个Button,而右边的Fragment中显示了一个TextView。在上面的例子中我们直接在布局中添加了两个fragment,让其平分整块屏幕。这种方式过于简单了,在实际项目中很少会用到。Fragment真正的用途是在程序运行时动态的添加到Activity中。
1.2 动态添加Fragment
我们接着在之前的项目上新建一个another_right_fragment.xml,它的布局和right_fragment布局基本一致,为了区分我们将another_right_fragment的背景颜色更改为黄色。:
<?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:background="#ffff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is another right fragment"
android:textSize="24sp" />
</LinearLayout>
然后我们为another_right_fragment.xml创建类文件AnotherRightFragment:
class AnotherRightFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.another_right_fragment, container, false)
}
}
现在我们已经创建了another_right_fragment的布局文件和它的类文件AnotherRightFragment,接下来就要开始动态的将它添加到Activity中了。首先我们修改activity_main.xml文件,它的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<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替换成了一个Frameayout
由于我们只希望在布局中放入一个Fragment,而不需要任何定位
所以使用这种布局十分的合适*/
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
接下来我们在代码中向FrameLayout中添加内容,从而实现动态添加Fragment的功能。修改MainActivity中的代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//设置LeftFragment按钮点击事件
button.setOnClickListener {
//点击按钮替换成AnotherRightFragment
replaceFragment(AnotherRightFragment())
}
//默认显示RightFragment
replaceFragment(RightFragment())
}
//替换Fragment
private fun replaceFragment(fragment: Fragment) {
//获得FragmentManager对象
val fragmentManager = supportFragmentManager
//开启一个事物
val transaction = fragmentManager.beginTransaction()
//通过replace()向容器内添加或替换Fragment
transaction.replace(R.id.frameLayout, fragment)
//提交事物
transaction.commit()
}
}
若想要实现动态添加Fragment,需要经历以下步骤:
①创建待添加的Fragment实例。
②获取FragmentManager。可以在Activity中可以通过getsupportFragmentManager()方法获取。
③通过FragmentManager的beginTransaction()方法开启一个事务。
④使用replace()方法添加或替换Fragment。replace()方法需要传入以下参数:1.将要被替换的控件id(这里要替换的控件是FrameLayout)2.新Fragment的对象
⑤通过调用commit()方法来提交事物。
程序运行效果如下图所示:
当我们点击左侧LeftFragment中的按钮后会将右侧的Fragment替换成新的Fragment。
1.3 实现Fragment返回栈
在上面的例子中,我们点击LeftFragment的按钮可以将右侧绿色RightFragment替换成黄色AnotherRightFragment,当我们按下Back返回键后程序就会退出。如果我们想实现类似于返回栈的效果,当我们按下Back返回键后可以从黄色AnotherRightFragment回到之前的绿色RightFragment该如何实现呢?
在FragmentManager中可以通过addToBackStack( )方法将一个事物添加到返回栈,我们修改MainActivity中的代码:
class MainActivity : AppCompatActivity() {
· · ·
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.frameLayout, fragment)
//将该事物添加到返回栈中
transaction.addToBackStack(null)
transaction.commit()
}
}
这样当我们按下Back返回键后就可以从黄色AnotherRightFragment回到之前的绿色RightFragment了。addToBackStack()方法的参数是事务的名称,这个名称可以用于在后续的操作中标识事务。
注意:在使用 addToBackStack() 方法时,应该在事务开始之前调用它,以确保事务被正确添加到回退堆栈中。
1.4 Fragment和Activity之间的交互
虽然Fragment是嵌入在Activity中的,但是他们是各自存在于一个独立的类当中的,所以它们之间的交互并不容易。为了方便Fragment和Activity之间进行交互,FragmentManager提供了一个较findFragmentById()的方法,该方法可以从布局文件中获取到Fragment的实例。例如以下代码就可以获取到activity_main.xml中名为leftFrag的fragment。
val myFragment = supportFragmentManager.findFragmentById(R.id.leftFrag)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
· · ·
tools:context=".MainActivity">
<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"
· · ·/>
</LinearLayout>
在上面的例子中我们学习了如何在Activity中调用Fragment的方法,那么如何在Fragment中调用Activity呢?我们可以在Fragment中通过getActivity()方法来得到和当前Fragment相关联的Activity实例:
if(activity !=null){
val mainActivity = activity as MainActivity
}
在这里我们通过getActivity()方法返回与当前Fragment关联的Activity实例。如果当前Fragment没有关联到任何Activity(例如:Fragment 还没有被添加到 Activity中)那么getActivity()会返回null。因此使用getActivity() 方法时,需要确保该Fragment 已经添加到了一个Activity中。
如果我们想要实现Fragment之间的通信,那么我们可以在一个Fragment中得到与它相关联的Activity,然后再通过这个Activity去获取另一个Fragment实例。
二、Fragment的使用
2.1 Fragment的生命周期
class RightFragment : Fragment() {
companion object {
const val TAG = "RightFragment"
}
//当Fragment和Activity建立关联的时候调用(只会调用一次)
override fun onAttach(context: Context?) {
super.onAttach(context)
Log.d(TAG, "onAttach()")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate()")
}
//未Fragment创建view组件(加载布局)时调用
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.right_fragment, container, false)
}
//确保与Fragment相关联的Activity已经创建完毕时调用
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")
}
//当与Fragment关联的视图被移除时调用
override fun onDestroyView() {
super.onDestroyView()
Log.d(TAG, "onDestroyView()")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
}
//当Fragment和Activity解除关联时调用
override fun onDetach() {
super.onDetach()
Log.d(TAG, "onDetach()")
}
}
点击运行后RightFragment第一次被加载到屏幕上:
点击LeftFragment中的Button按钮后,AnotherRightFragment替换了RightFragment:
按下返回键,RightFragment再次被显示出来:
再次按下返回键退出程序:
Fragment中也是可以通过onSaveInstanceState() 方法来保存数据,因为进入停止状态的Fragment也可能在系统内存不足时被回收。保存下来的数据可以在onCreate()、onCreateView() 和 onActivityCreated() 这3个方法中重新恢复数据。它们都包含一个Bundle类型的saveInstanceState参数。具体如何使用可以参考第七章的“1.4.3 Activity被回收了怎么办”中的内容。
2.2 Android中常见的限定符
屏幕尺寸:
layout-small 小屏幕设备
layout-normal 基准屏幕设备
layout-large 大屏幕设备
layout-xlarge 超大屏幕设备
屏幕密度:
layout-ldpi <=120dpi(低分辨率设备)
layout-mdpi <= 160dpi(中分辨率设备)
layout-hdpi <= 240dpi(高分辨率设备)
layout-xhdpi <= 320dpi(超高分辨率设备)
layout-xxhdpi <= 480dpi(超超高分辨率设备)
layout-xxhdpi <= 640dpi(只用来存放icon)
layout-nodpi 与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸
layout-tvdpi 介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不需要关心这个密度值
屏幕方向:
layout-land 横向屏幕设备
layout-port 纵屏幕设备
2.3 使用限定符动态加载布局
平板由于屏幕大,所以通常将采用双页模式(左边和右边显示两页内容)。但手机的屏幕比较小,只能显示一页内容。为了在运行的时候让程序判断是应该采用双页模式还是单页模式,我们可以通过限定符来实现。修改activity_main.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="horizontal">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
//只保留左侧Fragment并让其充满整个父布局
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
在res文件夹下新建layout-large文件夹,并在其中新建一个activity_main.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="horizontal">
<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.xml布局中只包含了一个Fragment,我们让其当作单页模式的布局。而layout-large/activity_main.xml布局中包含两个Fragment,作为双页模式的布局。这里面的large其实就是一个限定符,那些屏幕尺寸被系统认为是large的设备就会自动加载layout-large目录下的布局,而小屏幕设备则还是会加载layout目录下的布局。
我们将MainActivity中replaceFragment()有关的代码注释掉,重新运行充程序。这样我们的程序就可以在运行的时候根据设备屏幕的大小自动显示相应的布局文件了。
2.4 使用最小宽度限定符加载布局
在上一节中我们使用了large限定符来让程序自动选择双页模式还是单页模式。那么large到底是指有多大呢?有时候我们希望灵活的为不同设备加载布局,不管它是被系统认定为large还是small。
这时我们就可以使用最小宽度限定符对屏幕宽度指定一个最小值(单位dp)。然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载A布局,屏幕宽度小于这个值就加载B布局。
我们在res文件夹下新建一个layout-sw300dp文件夹,然后将large/activity_main.xml布局复制过来。这样当屏幕宽度大于300dp的设备上运行的时候就会加载layout-sw300dp文件夹中的布局,当屏幕宽度小于300dp时就会加载默认的layout/activity_main.xml布局文件。以下是在一款屏幕宽度为320dp的手机上运行后的效果:
可以看到,虽然这款手机的屏幕宽度并不符合large限定符的定义,但是我们通过最小宽度限定符让它使用了双叶模式。通过最小宽度限定符你就可以灵活的在不同设备上自动选择合适的布局了!