《第一行代码》第三版之探究Fragment(六)

       在本章,我们首先介绍了Fragment的使用方式:简单用法、FragmentManager和Transaction动态添加、返回栈防止退出、Fragment和Activity之间的信息传递。随后介绍了Fragment生命周期、限定符和最小宽度限定符。最后我们介绍了扩展函数(ClassName.methodName)和运算符(operator fun plus())。
5.1.Fragment是什么
      手机屏幕在3英寸~6英寸之间,平板在7英寸~10英寸之间,屏幕大小差距过大会导致元素过分拉长、元素空隙过大的问题。因此引入Fragment概念。Fragment是嵌入在Activity当中的UI片段,能让程序更合理和充分地利用大屏空间,可以理解为迷你型Activity。
5.2.Fragment的使用方式
       建立Pixel C平板播放器。新建FragmentTest项目。
5.2.1.Fragment简单用法
       新建左侧Fragment布局left_fragment.xml和右侧right_framgent.xml。




    



    

       在建立LeftFragment和RightFragment类,基于Fragment加载刚才的布局xml。

package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

//在这里一定要使用androidX的Fragment,因为他可以让Fragment的特性在所有Android系统中保持一致,系统内置的在9.0版本被废除。
class LeftFragment : Fragment() {
    //仅仅重写了onCreateView方法
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //通过LayoutInflater将方才定义的left_fragment加载进来
        return inflater.inflate(R.layout.left_fragment, container, false)
    }
}
package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
//同样方式使用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里面的代码,通过Fragment标签加载Fragment。




    
    

5.2.2.动态加载Fragment
     Fragment强大之处在于在程序运行时动态将其添加到Activity当中,下面来研究下。新建another_right_fragment.xml。



    

     新建AnotherRightFragment类作为另一侧需要动态添加的Fragment。

package com.example.myapplication

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的布局,确保能够动态映射到此布局。



    
    

    

     最后在MainActivity中向刚才的Framelayout中添加内容,基于FragmentManager的transaction事务。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.left_fragment.*
//完成动态添加Fragment的功能,五步走战略
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //第一步,创建添加Fragment的实例
        left_button.setOnClickListener{
            replaceFragment(AnotherRightFragment())
        }
        replaceFragment(RightFragment())
    }

    fun replaceFragment(fragment:Fragment){
        //第二步,获取FragmentManager
        val fragmentManager = supportFragmentManager
        //第三步,开启一个事务
        val transaction = fragmentManager.beginTransaction()
        //第四步,向容器内添加或者替换Fragment,一般使用replace来代替,传递id和添加的Fragment实例
        transaction.replace(R.id.frame_layout,fragment)
        //第五步,提交事务
        transaction.commit()
    }
}

5.2.3.在Fragment中实现返回栈
      点击Back键退出了程序,那么想要点击back键回到上一个Fragment。实现的方法是:FragmentTransaction中提供了addToBackStack方法,可以用于将一个事务添加到返回栈中。

    fun replaceFragment(fragment:Fragment){
        ...
        transaction.replace(R.id.frame_layout,fragment)
        transaction.addToBackStack(null)
        //第五步,提交事务
        transaction.commit()
}

5.2.4.Fragment和Activity之间的交互
       虽然说是嵌入,但其实分属不同类。如果Activity想调用Fragment里面的方法或者是Fragment想调用Activity里面的方法,该咋办?
       为了方便交互,FragmentManager提供了一个类似于findViewByID的方法,专门从布局文件中获取Fragment的实例,代码如下所示。

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

        这样可以在Activity中简单获取Fragment的实例,轻松调用Fragment里面的方法。Kotlin-android-extensions中对findFragmentById方法进行扩展,允许直接使用布局文件中的id名称直接获取相应的Fragment实例,简化代码如下:

  val fragment = left_frag as LeftFragment 

       Fragment如何调用Activity里面的方法呢?每个Fragment当中都可以通过getActivity获得和当前Fragment相关联的实例,代码如下:

        //MainActivity可能为null,因此需要判空处理
        if(activity!=null){
            //获得Activity实例,当需要context对象时,也可以使用getActivity方法
            val mainActivity = activity as MainActivity
        }

       两个Fragment之间如何通信?Fragment找Activity,Activity获取另一个Fragment实例,这样完成了通信功能。
5.3.Fragment生命周期
       有四种状态:运行、暂停、停止和销毁四种状态。运行状态:Fragment相关联的Activity正在运行;暂停:当一个Activity进入暂停状态(另一个为占满屏幕的Activity进入栈顶),则与此相关联的Fragment也暂停;停止:Activity停止或者FragmentTransaction的remove和replace移除被调用,但在此之间调用了addToBackStack方法;终止:Activity被销毁或者FragmentTransaction的remove和replace移除被调用。未调用addToBackStack方法。
       几个回调方法:onAttach:Fragment和Activity建立关联;onCreateView:为Fragment创建视图时调用;onActivityCreated:确保与Fragment相关联的Activity已经创建完毕时调用;onDestroyView:与Fragment关联的视图被移除时调用;onDetach:两者解除关联时调用。

                                                                  《第一行代码》第三版之探究Fragment(六)_第1张图片
5.3.2.体验Fragment生命周期

修改RightFragment里面的代码:

package com.example.myapplication

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

//同样方式使用Fragment加载右布局
class RightFragment : Fragment() {

    companion object {
        const val TAG = "RightFragment"
    }

    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,"onCreate")
    }

    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第一次被显式在屏幕上时。

《第一行代码》第三版之探究Fragment(六)_第2张图片
     当替换Fragment时,RightFragment停止了,此时添加了addToBackStack方法就会显示如下:


     如果没有调用,RightFragment会被销毁,onDestroy和onDetach将会被调用。此时点击Back键的显示如下:


     再点击Back键会退出:


5.4.动态加载布局的技巧
   添加、替换功能太简单,如果能根据设备分辨率或者屏幕大小选择运行时加载那个布局,那么比较有意思。
5.4.1.使用限定符
       平板一般是双页模式(左边列表、右边内容),手机一般是单页,两页分开。如何确定应该是单页双页?需要借助限定符qualifier来实现。比如large限定符。
       修改FragmentTest项目里的Activity_main.xml文件:



    
    

      接着新建layout-large文件夹,同样新建布局activity_man.xml,建立目录过程如下:(1)鼠标右击app/res->New->Android Resource Directory;(2)右击layout-large->New->ayout resource file。参考链接和代码示例如下:参考链接



    
    

       注释相关的无用代码,此时,平板上是双屏,手机上是单屏。常用限定符如下:屏幕大小的small、normal、large和xlarge;分辨率大小的ldpi(<120)、mdpi(120~160)、hdpi(160~240)、xhdpi(240~320)以及xxhdpi(320~480);方向land(横屏)和port(竖屏)
5.4.2.使用最小宽度限定符
       使用large解决了单双页判断问题,但large多大是个问题?我们在这里使用最小宽度限定符来解决。其是对屏幕的宽度指定一个最小值(以dp为单位),然后以其为临界点。
       我们建立layout-sw600dp文件夹和activity_main.xml布局,代码和上面的一模一样。此意味着,当屏幕宽度大于等于600dp时,回家再layout-sw600dp/activity_main布局,否则加载layout/activity_main布局。
5.5.Kotlin之扩展函数和运算符重载
5.5.1.扩展函数

       扩展函数是指在不改边某个类源码的情况下,依然可以打开这个类,并向该类提供新的函数。举例来讲:字符串包含字母、数字和特殊符号,统计字符串中字母的数量,实现代码如下所示:

package com.company

object StringUtil {
    fun letterCount(str: String): Int {
        var count = 0
        for (char in str) {
            if (char.isLetter()) {
                count++
            }
        }
        return count
    }
}
package com.company

fun main(){
    val str = "ABC!@#Xxyerw!@#"
    println(StringUtil.letterCount(str))
}

       扩展函数的语法结构如下,相比于定义普通函数,我们只需要在前面加上一个ClassName.的语法结构,就可以将该函数添加到指定类中。

fun ClassName.methodName(param1:Int,param2:Int):Int{
    return 0
}

      下面往String类中添加一个扩展函数,首先创建String.kt文件,将其定义成顶层方法一遍拥有全局访问域。

package com.company

fun String.letterCount(): Int {
    var count = 0
    for (char in this) {
        if (char.isLetter()) {
            count++
        }
    }
    return count
}

     访问代码如下,看起来就像String类自带了lettersCount方法一样。

println(str.letterCount())

5.6.2.有趣的运算符重载
       运算符重载是Kotlin中一个有趣的语法。运算符包括+-*/%等,Kotlin运算符重载允许任意两个对象相加或者更多其他操作。譬如两个Money对象相加。
      运算符重载使用的是operator关键字,在指定函数前面使用operaor关键字,就可以实现运算符重载了。譬如加号运算符对应的是plus函数,减号对应的是minus函数。举例,完成两个Money对象的相加。

package com.company

//主构造函数接受一个value参数,用于表示钱的金额
class Money(val value: Int) {
    //使用operator关键来修饰plus函数是必不可少,将两个对象的value值相加,得到和后封装返回
    operator fun plus(money: Money): Money {
        val sum = value + money.value
        return Money(sum)
    }
   //仅仅单功能肯定不方便,Kotlin、允许我们对同一运算符进行多重重载,接受整型数字
    operator fun plus(newValue: Int): Money {
        val sum = value + newValue
        return Money(sum)
    }
}

       Main函数调用代码如下:

package com.company

fun main() {
    val money1 = Money(5)
    val money2 = Money(10)
    val money3 = money1 + money2
    println(money3.value)
}

                                              《第一行代码》第三版之探究Fragment(六)_第3张图片    

      举例来讲,我们在前面使用了一个随机生成字符串长度的函数,如果使用str*n岂不是很棒?

    fun getRandomLengthString(str: String): String {
        val n = (1..20).random()
        var builder = StringBuilder()
        repeat(n) {
            builder.append(str)
        }
        return builder.toString()
    }

       在String.kt添加如下代码:

//operator关键字必不可少,times对应*,定义扩展函数使用了String.。
operator fun String.times(n: Int): String {
    var builder = StringBuilder()
    repeat(n) {
        builder.append(this)
    }
    return builder.toString()
}

       调用代码:

val str = "abc" * 10
println(str)

       需要说明的是String类已经提供了将字符串重复n遍的repeat函数,因此可以进一步精简为:

operator fun String.times(n: Int): String = repeat(n)

       最后重复任意次数的字符串代码如下:

fun getRandomLengthString(str: String): String = str * (1..20).random()


 

你可能感兴趣的:(第一行代码第三版)