平板手机要兼顾,探究Fragment
5.1 Fragment是什么
Fragment是一种可以嵌入在Activity当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。它能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity片段。
Fragment是Android3.0引入的新API,可以把Fragment想成Activity中的模块,这个模块有自己的布局,有自己的生命周期,单独处理自己的输入,在Activity运行的时候可以加载或者移除Fragment模块。 可以把Fragment设计成可以在多个Activity中复用的模块,当开发的应用程序同时适用于平板电脑和手机时,可以利用Fragment实现灵活的布局,改善用户体验。
5.2 Fragment的使用
1. Fragment的简单用法
新建一个left_fragment文件(左边frgment的布局)
新建一个right_fragment文件(右边frgment的布局)
代码如下:
//左边
<?xml version="1.0" encoding="utf-8"?>
<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/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"/>
</LinearLayout>
//右边
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
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="This is right frgment"/>
</LinearLayout>
新建左边类 LeftFragment,新建右边类RightFrament
(将刚刚的布局动态加载)
代码如下:
//左边
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)
}
}
最后在activity_main里面注册
<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>
2. 动态添加Fragment
//创建一个AnotherRightFragment类和一个布局文件
class AnotherRightFragment :Fragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.another_right_fragment,container,false)
}
}
<?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="This is anohter right frgment"/>
</LinearLayout>
//使用FrameLayout 放入Fragment
<?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 //FrameLayout替换fragment
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
//修改MainActivity 实现动态添加Fragment的功能
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的步骤:
3. 在Fragment中实现返回栈
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.addToBackStack(null)
//使用FragmentTransaction的addToBackStack()方法
transaction.commit()
}
}
4. Fragment和Activity之间的交互
Activity里调用Fragment的方法
- 第一种
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment
调用FragmentManager的findFragmentById()方法可以在Activity中得到相应的Fragment实例,然后就可以调用Fragment的方法了 - 第二种
val fragment = leftFrag as LeftFragment
kotlin中的插件允许我们直接使用布局文件中定义的Fragment id 名 自动获取Fragment实例
Fragment里调用Activity的方法
if(activity != null){
val mainActivity = activity as MainActivity
}
通过getActivity()方法来得到和当前Fragment相关联的Activity
由于getActivity()方法可能会返回null 所以这边进行判空处理
5.3Fragment的生命周期
1. Fragment的状态和回调
- 运行状态
Fragment所关联的Activity 处于运行状态,该Fragment也处于运行状态 - 暂停状态
Fragment所关联的Activity 处于暂停状态,该Fragment也处于暂停状态 - 停止状态
Fragment所关联的Activity 处于停止状态,该Fragment也处于停止状态或者Fragment从Activity中移除,在事务提交之前用了addToBackStack()方法,这时的Fragment也会进入停止状态 - 销毁状态
Fragment所关联的Activity 处于销毁状态,该Fragment也处于销毁状态或者Fragment从Activity中移除,在事务提交之前没用了addToBackStack()方法,这时的Fragment也会进入停止状态
回调方法:
onAttach() 当Fragment和Activity建立关联时调用。
onCreateView() 为Fragment创建视图(加载布局)时调用。
onActivityCreated() 确保与Fragment相关联的Activity已经创建完毕时调用。
onDestroyView() 当与Fragment关联的视图被移除时调用。
onDetach() 当Fragment和Activity解除关联时调用。
2. 体验Fragment的生命周期
class RightFragment: Fragment() {
companion object{
const val TAG = "RightFragment"
}
override fun onSaveInstanceState(outState: Bundle) {
//该方法用于数据的存储 防止进入停止状态的Fragment系统内存不足时被回收
super.onSaveInstanceState(outState)
val tempData = "something you just typed"
outState.putString("data_key",tempData)
}
override fun onAttach(context: Context) {
super.onAttach(context)
Log.d(TAG,"onAttach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null){
val tempData = savedInstanceState.getString("data_key")
//保存的数据在该方法中可以重新得到
//onCreateView() onActivityCreated()也可以获得
Log.d(TAG,tempData)
}
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")
}
}
5.4 动态加载布局的技巧
1. 使用限定符
//修改activity_main 作为单页模式使用
<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"
android:layout_weight="1" />
</LinearLayout>
//新建large\activity_main(layout-large 文件夹) 作为双页模式使用
<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>
//最后需要将MainAcitvity里的replaceFragment()里的代码注释掉
large是一个限定符 那些被认为是large的设备会自动加载layout-large文件下的布局
2.使用最小宽度限定符
最小宽度限定符允许我们对屏幕指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局
//新建layout—sw600dp 文件夹中新建activity_main.xml
<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里的布局否则就说layout/activity_main布局
5.5 Fragment的最佳实践:一个简易的新闻应用
由于代码量比较多,书中也有,这边就不写了。
有需要源代码的话可以去关注微信公众号“郭霖” 领取随书资源
这边仅说明一下,个人在实现的时候,出现的一些问题
- item的android:layout_height="wrap_content"写成android:layout_height=“match_parent”
结果如图:
- layout-sw600dp 创建错误结果一直报错
错误原因:
cannot find the declaration of element ‘ConstraintLayout’
解决办法:
在res下的layout文件夹名改为layout-xxxx
(使用new->Android Resource Diretory创建)
3.getStringExtra用成getStringArrayExtra
结果:
5.6 kotlin课堂:拓展函数和运算符重载
1.大有用途的扩展函数
Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法。在Kotlin源码中,有大量的扩展函数来扩展java,这样使得Kotlin比java更方便使用,效率更高。通常在java中,我们是以各种XXXUtils的方式来对已经存在的类进行功能的扩展。但是有了扩展函数,我们就能丢弃让人讨厌的XXXUtils方法工具类。
语法如下:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
下面我们为String类添加一个扩展函数(统计字母数)
书中建议向哪个类添加扩展函数,就定义一个同名的.kt文件,并且定义为顶层函数
fun String.lettersCount(): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
fun main() {
val stringcount = "abc123*-".lettersCount()
println(stringcount)
}
//输出结果 3
2. 有趣的运算符重载
Kotlin的运算符重载(关键字:operator)允许我们让任意两个对象进行相加,或者是进行更多其他的运算操作。
这里以加号运算符为例,如果想要实现让两个对象(运算符重载在类种)相加的功能,那么它的语法结构如下:
class Obj {
operator fun plus(obj: Obj): Obj {
// 处理相加的逻辑
}
}
//对应的调用方式
val obj1 = obj()
val obj2 = obj()
val obj3 = obj1 = obj2
//对象与对象相加
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
//对象与数字直接相加
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
fun main(){
val m1 = Money(5)
val m2 = Money(5)
val m3 = m2 + m1
val m4 = m1 + 20
println(m3.value)
println(m4.value)
}
运算符语法糖表达式和实际调用函数对照表:
需要更详细的话可以看:https://www.jianshu.com/p/d445209091f0