当今是移动设备发展很是迅速的时代,不只手机已经称为了生活必需品,并且平板也变得愈来愈普及。平板和手机最大的区别就在于屏幕的大小:通常手机屏幕的大小在 3 英寸到 6 英寸之间,平板屏幕的大小在 7 英寸到 10 英寸之间。屏幕大小差距过大有可能会让一样的界面在视觉效果上有较大的差别,好比一些界面在手机上看起来很是美观,但在平板上看起来可能会有控件被过度拉长、元素之间空隙过大等状况。android
对于一名专业的 Android 开发人员而言,可以兼顾手机和平板的开发是咱们很可能要左到的事情。Android 3.0 版本开始引入了 Fragment 的概念,它可让界面在平板上更好地展现,下面咱们就一块儿来学习一下。bash
Fragment 是一种能够嵌入在 Android 当中的 UI 片断,它能让程序更加合理和充分地利用大屏幕的空间,于是在平板上应用得很是普遍。Fragment 和 Activity 很是像,一样能够包含布局,一样都有本身的生命周期。你甚至能够将 Fragment 理解成一个迷你型的 Activity,虽然这个迷你型的 Activity 有可能和普通的 Activity 是同样大的。app
在 Activity 中添加两个 Fragment,并让这两个 Fragment 平分 Activity 的空间。ide
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>
复制代码
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="#0f0"
android:orientation="vertical">
<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>
复制代码
建立 LeftFragmentui
package com.example.fragmenttest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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)
}
}
复制代码
这里须要继承 Fragment 类,并重写 onCreateView
方法,这里须要注意的是:要继承 androidx
包中的 Fragment。而后经过 inflater
加载布局文件。spa
建立 RightFragment插件
package com.example.fragmenttest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class RightFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.right_fragment, container, false)
}
}
复制代码
修改 activity_main.xml3d
<?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"
tools:context=".MainActivity">
<fragment
android:id="@+id/leftFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.fragmenttest.LeftFragment"/>
<fragment
android:id="@+id/rightFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.fragmenttest.RightFragment"/>
</LinearLayout>
复制代码
在这里添加两个 fragment
平分屏幕的空间。
注意:添加 fragment
时须要经过 name
属性显式的指定要添加的 Fragment 类。
在上一节中,你已经学会了在布局文件中添加 Fragment 的方法,不过 Fragment 真正的强大之处在于,它能够在程序运行时动态地添加到 Activity 中。根据具体状况来动态地添加 Fragment,你就能够将程序界面定制的更加多样化。
建立 another_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:orientation="vertical"
android:background="#ff0">
<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
package com.example.fragmenttest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.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
<?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"
tools:context=".MainActivity">
<fragment
android:id="@+id/leftFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.fragmenttest.LeftFragment"/>
<FrameLayout
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</FrameLayout>
</LinearLayout>
复制代码
将右侧的 fragment
替换成 FrameLayout
,该布局会默认将全部控件都摆放在布局的左上角。
修改 MainActivity
package com.example.fragmenttest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.left_fragment.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
replaceFragment(RightFragment())
}
/**
* 动态的替换 fragment
*/
private fun replaceFragment(fragment: Fragment) {
// 获取 fragmentManager
val fragmentManager = supportFragmentManager
// 开启事务
val transaction = fragmentManager.beginTransaction()
// 将传递过来的 fragment
transaction.replace(R.id.rightLayout, fragment)
// 提交事务
transaction.commit()
}
}
复制代码
实现返回栈功能
/**
* 动态的替换 fragment
*/
private fun replaceFragment(fragment: Fragment) {
// 获取 fragmentManager
val fragmentManager = supportFragmentManager
// 开启事务
val transaction = fragmentManager.beginTransaction()
// 将传递过来的 fragment
transaction.replace(R.id.rightLayout, fragment)
// 将事务添加到返回栈
transaction.addToBackStack(null)
// 提交事务
transaction.commit()
}
复制代码
若是以以前的方式实现的话点击了返回按钮后就会直接退出应用程序,若是使用上面的方式就能够将事务添加到返回栈中。
虽然 Fragment 是嵌入在 Activity 中显示的,但是它们的关系并无那么亲密,实际上 Fragment 和 Activity 是各自存在于一个独立的类中的,它们之间并无那么明显的方式来直接进行交互。
为了方便 Fragment 和 Activity 之间进行交互,FragmentManager 提供了一个相似于 findViewById()
的方法,专门用于从布局文件中获取 Fragment 的实例:
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment
复制代码
findFragmentById()
方法能够在 Activity 中获得相应的 Fragment 的实例,而后就能轻松地调用 Fragment 的方法了。
另外,相似于 findViewById()
方法,Kotlin 的安卓扩展插件也对 findFragmentById()
进行了扩展,容许咱们直接使用布局文件中定义的 Fragment id 名称来自动获取相应的 Fragment 实例:
val fragment = leftFrag as LeftFragment
复制代码
在每一个 Fragment 中均可以经过调用 getActivity()
方法来获得和当前 Fragment 关联的 Activity 实例。
if (activity != null) {
val mainActivity = activity as MainActivity
}
复制代码
除此以外,当 Fragment 中须要使用 Context 对象时,也可使用 getActivity()
方法,由于获取到的 Activity 自己就是一个 Context
对象。
Fragment 与 Fragment 之间进行通讯的思路很简单,只须要使用当前 Fragment 获取与它相关联的 Activity,再经过这个 Activity 去获取另一个 Fragment 实例,这便实现了 Fragment 与 Fragmnet 之间的通讯。
Activity 的生命周期包括 运行状态、暂停状态、中止状态、销毁状态。Fragment 的生命周期与之很是相似,每一个 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 也会进入销毁状态。
Fragment 在 Activity 的基础之上又增长了几个回调方法:
package com.example.fragmenttest
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class RightFragment : Fragment() {
companion object {
const val TAG = "RightActivity"
}
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: ")
}
}
复制代码
当应用启动后,会在控制台打印:
当点击按钮后,RightFragment 会被替换,这时控制台会打印:
addToBackStack()
方法,此时 RightFragment 就会进入销毁状态,
onDestroy()、onDetach()
就会被执行。
点击 Back 按钮后:
再次点击 Back 按钮:
值得一提的是,在 Fragment 中能够经过 onSaveInstanceState()
方法来保存数据,由于进入中止状态的 Fragment 有可能在系统内存不足的时候被回收,保存下来的数据在 onCreate()、onCreateView()
和 onActivityCreated()
这3个方法中均可以从新获得,它们都包含一个 Bundle
类型的 savedInstanceState
参数。
虽然动态添加 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"
tools:context=".MainActivity">
<fragment
android:id="@+id/leftFrag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.fragmenttest.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.fragmenttest.LeftFragment"/>
<fragment
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:name="com.example.fragmenttest.RightFragment"/>
</LinearLayout>
复制代码
能够看到,layout/activity_main 布局只包含了一个 Fragment,即单页模式, 而layout-large/activity_main 布局则包含两个 Fragment,即双页模式,其中 large
就是限定符。屏幕被识别为 large 的设备就会自动加载 layout-large 下的布局,小屏幕的设备则仍是会加载 layout 文件夹下的布局。
注释掉 replaceFragment()
中的代码。
运行效果: 平板模拟器
Android 中常见的限定符
在前面咱们成功的使用了 large 限定符解决了单页双页的判断问题,不过很快又有一个新的问题出现了,large 究竟是指多大呢?有时候咱们能够更加灵活的为不一样设备加载布局,无论它们是否是被系统认定为 large,这时就可使用最小宽度限定符(smallest-width qualifer)。
最小宽度限定符容许咱们对屏幕的宽度指定一个最小值(以 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:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/leftFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:name="com.example.fragmenttest.LeftFragment"/>
<fragment
android:id="@+id/rightFrag"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:name="com.example.fragmenttest.RightFragment"/>
</LinearLayout>
复制代码
若是屏幕大于 600dp 就会加载 layout-sw600dp/activity_main.xml, 屏幕小于 600dp 就会加载 layout/activity_main.xml 布局。