当今是挪动设施倒退十分迅速的时代,不仅手机曾经称为了生活必需品,而且平板也变得越来越遍及。平板和手机最大的区别就在于屏幕的大小:个别手机屏幕的大小在 3 英寸到 6 英寸之间,平板屏幕的大小在 7 英寸到 10 英寸之间。屏幕大小差距过大有可能会让同样的界面在视觉效果上有较大的差别,比方一些界面在手机上看起来十分好看,但在平板上看起来可能会有控件被过分拉长、元素之间空隙过大等状况。
对于一名业余的 Android 开发人员而言,可能兼顾手机和平板的开发是咱们极可能要左到的事件。Android 3.0 版本开始引入了 Fragment 的概念,它能够让界面在平板上更好地展现,上面咱们就一起来学习一下。
1.Fragment 是什么
Fragment 是一种 能够嵌入在 Android 当中的 UI 片段,它能让程序更加正当和充沛地利用大屏幕的空间,因此在平板上利用得十分宽泛。Fragment 和 Activity 十分像,同样能够蕴含布局,同样都有本人的生命周期。你甚至能够将 Fragment 了解成一个迷你型的 Activity,尽管这个迷你型的 Activity 有可能和一般的 Activity 是一样大的。
2.Fragment 的应用办法
2.1 Fragment 的简略用法
在 Activity 中增加两个 Fragment,并让这两个 Fragment 平分 Activity 的空间。
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>`
创立 LeftFragment
`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
加载布局文件。
创立 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.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"/>
<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 类。
2.2 动静增加 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()
}
`
如果以之前的形式实现的话点击了返回按钮后就会间接退出应用程序,如果应用下面的形式就能够将事务增加到返回栈中。
2.3 Fragment 与 Activity 之间的交互
2.3.1 在 Activity 中获取 Fragment
尽管 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
2.3.2 在 Fragment 中获取 Activity
在每个 Fragment 中都能够通过调用 getActivity()
办法来失去和以后 Fragment 关联的 Activity 实例。
`if (activity != null) {
val mainActivity = activity as MainActivity
}`
除此之外,当 Fragment 中须要应用 Context 对象时,也能够应用 getActivity()
办法,因为获取到的 Activity 自身就是一个 Context
对象。
2.3.3 Fragment 与 Fragment 之间进行通信
Fragment 与 Fragment 之间进行通信的思路很简略,只须要应用以后 Fragment 获取与它相关联的 Activity,再通过这个 Activity 去获取另外一个 Fragment 实例,这便实现了 Fragment 与 Fragmnet 之间的通信。
2.4 Fragment 的生命周期
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 的根底之上又减少了几个回调办法:
- onAttack():当 Fragment 和 Activity 建设关联时 调用。
- onCreateView():为 Fragment 加载布局时 调用。
- onActivityCreated():确保与 Fragment 相关联的 Activity 曾经 创立结束 时调用。
- onDestroyView():当与 Fragment 关联的视图被移除时 调用。
- onDetach():当 Fragment 和 Activity 解除关联时 调用。
2.4.1 体验 Fragment 的生命周期
`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
参数。
2.5 动静加载布局的技巧
尽管动静增加 Fragment 的性能很弱小,能够解决很多理论开发中的问题,然而它毕竟只是在一个布局文件中进行一些增加和替换操作。如果程序可能依据设施的分辨率或屏幕大小,在运行时决定加载哪个布局,那咱们能够发会的空间就更多了。
2.5.1 应用限定符
批改 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 中常见的限定符
2.5.3 应用最小宽度限定符
在后面咱们胜利的应用了 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 布局。
一点题外话:
咱们有《Android 学习、面试;文档、视频资源收费获取》,可复制链接后用石墨文档 App 或小程序关上链接或者私信我材料支付。
https://shimo.im/docs/TG8PDh9…