UI fragment 与 fragment 管理器

  1. 创建Crime数据类(Crime.kt)

    1. 导入类包时,在确认应导入哪个版本的Date类时,选择java.util.Date类。 UUID是Android框架里的工具类。有了它,生成唯一ID值就方便多了。在构造函数里,调 用UUID.randomUUID()生成一个随机唯一ID值。 使用默认的Date构造函数初始化Date变量。设置Date变量值为当前日期,作为crime的默认发生时间。
      data class Crime(val id: UUID = UUID.randomUUID(),
      var title: String = "",
      var date: Date = Date(),
      var isSolved: Boolean = false)
                                                     
      Data classes 参考网址

  2. 创建UI fragment

    1. 定义CrimeFragment的布局, 添加字符串资源(res/values/strings.xml)
      
      <resources>
      <string name="app_name">CriminalIntent</string>
      <string name="crime_title_hint">Enter a title for the crime.</string>
      <string name="crime_title_label">Title</string>
      <string name="crime_details_label">Details</string>
      <string name="crime_solved_label">Solved</string>
      </resources>
                                      

    2. fragment视图的布局文件(res/layout/fragment_crime.xml)
       
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:tools="http://schemas.android.com/tools"
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_margin="16dp">
      <TextView
              style="?android:listSeparatorTextViewStyle"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/crime_title_label"/>
      <EditText
              android:id="@+id/crime_title"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:hint="@string/crime_title_hint"/>
      <TextView
              style="?android:listSeparatorTextViewStyle"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/crime_details_label"/>
      <Button
              android:id="@+id/crime_date"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              tools:text="Wed Nov 14 11:56 EST 2018"/>
      <CheckBox
              android:id="@+id/crime_solved"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/crime_solved_label"/>
      </LinearLayout>
                                      

    3. 创建CrimeFragment类. 修改代码继承Fragment类时,Android Studio会找到两个同名Fragment 类:android.app.Fragment和androidx.fragment.app.Fragment。前者是Android操作系统内置版 Fragment,而我们要用的是Jetpack库版Fragment,所以选择后者。
      class CrimeFragment : Fragment() {
          private lateinit var crime: Crime
          private lateinit var titleField: EditText
          private lateinit var dateButton: Button
          private lateinit var solvedCheckBox: CheckBox
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              crime = Crime()
          }
          override fun onCreateView(
              inflater: LayoutInflater,
              container: ViewGroup?,
              savedInstanceState: Bundle?
          ): View? {
              val view = inflater.inflate(R.layout.fragment_crime, container, false)
              titleField = view.findViewById(R.id.crime_title) as EditText
              dateButton = view.findViewById(R.id.crime_date) as Button
              solvedCheckBox = view.findViewById(R.id.crime_solved) as CheckBox
              dateButton.apply {
                  text = crime.date.toString()
                  isEnabled = false
              }
              return view
          }
          override fun onStart() {
              super.onStart()
              val titleWatcher = object : TextWatcher {
                  override fun beforeTextChanged(
                      sequence: CharSequence?,
                      start: Int,
                      count: Int,
                      after: Int
                  ) {
      // This space intentionally left blank
                  }
                  override fun onTextChanged(
                      sequence: CharSequence?,
                      start: Int,
                      before: Int,
                      count: Int
                  ) {
                      crime.title = sequence.toString()
                  }
                  override fun afterTextChanged(sequence: Editable?) {
      // This one too
                  }
              }
              titleField.addTextChangedListener(titleWatcher)
              solvedCheckBox.apply {
                  setOnCheckedChangeListener { _, isChecked ->
                      crime.isSolved = isChecked
                  }
              }
          }
      
      }
                                                     
         Fragment.onCreate(Bundle?)是公共函数,而Activity.onCreate(Bundle?)是受保护函 数。(如果没有可见性修饰符,那么Kotlin函数默认是公共的。)Fragment.onCreate(Bundle?)函 数及其他Fragment生命周期函数必须是公共函数,因为托管fragment的activity要调用它们。

         fragment的视图并没有在Fragment.onCreate(Bundle?)函数中生成。 虽然我们在该函数中配置了fragment实例,但创建和配置fragment视图是另一个Fragment生命周期函数完成 的:onCreateView(LayoutInflater, ViewGroup?, Bundle?)。 该函数会实例化fragment视图的布局,然后将实例化的View返回给托管activity。LayoutInflater及 ViewGroup是实例化布局的必要参数。Bundle用来存储恢复数据,可供该函数从保存状态下重建视图。

         在onCreateView(...)函数中,fragment的视图是直接通过调用LayoutInflater.inflate(...)函数 并传入布局的资源ID生成的。第二个参数是视图的父视图,我们通常需要父视图来正确配置部件。第 三个参数告诉布局生成器是否立即将生成的视图添加给父视图。这里传入了false参数,因为fragment 的视图将由activity的容器视图托管。

         视图状态在onCreateView(...)之后和onStart()之前恢复。视图状态一恢复,EditText的内容就要 用crime.title的当前值重置。这时候,如果有针对EditText的监听器(比如在onCreate(...) 或onCreateView(...)当中),那么TextWatcher的 beforeTextChanged(...)、onTextChanged(...)和 afterTextChanged(...)函数就会执行。 在onStart()里设置监听器可以避免这种情况的发生,因为视图状态恢复后才会触发监听器事件

  3. 托管UI fragment

    1. 定义容器视图, 创建fragment容器布局(res/layout/activity_main.xml)
      
      <androidx.fragment.app.FragmentContainerView
              xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/fragment_container"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:name="a.a.CrimeFragment" />
                                           
    2. 向FragmentManager中添加UI fragment, 添加一个CrimeFragment(MainActivity.kt)
       class MainActivity : AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              val currentFragment =
                  supportFragmentManager.findFragmentById(R.id.fragment_container)
              if (currentFragment == null) {
                  val fragment = CrimeFragment()
                  supportFragmentManager
                      .beginTransaction()
                      .add(R.id.fragment_container, fragment)
                      .commit()
              }
          }
      }
                                                        
        为了以代码的方式把fragment添加给activity,这里显式调用了activity的FragmentManager。我们使用 supportFragmentManager属性就能获取activity的fragment管理器。 因为使用了Jetpack库版本的fragment和AppCompatActivity类,所以这里用的是supportFragmentManager。

        fragment事务被用来添加、移除、附加、分离或替换fragment队列中的fragment。 FragmentManager维护着一个fragment事务回退栈,你可以查看、历数它们。如果fragment事务包含多个操作, 那么在事务从回退栈里移除时,其批量操作也会回退。基于这个原因,UI状态更好控制了。 FragmentManager.beginTransaction()函数创建并返回FragmentTransaction实例。 FragmentTransaction类支持流接口(fluent interface)的链式函数调用,以此配置FragmentTransaction再返回它。 因此,以上灰底代码可解读为:“创建一个新的fragment事务, 执行一个fragment添加操作,然后提交该事务。” add(...)函数是整个事务的核心,它有两个参数:容器视图资源ID和新创建的CrimeFragment。 容器视图资源ID你应该很熟悉了,它是定义在activity_main.xml中的FrameLayout部件的资源ID。

      创建 fragment 参考网址