當今是移動設備發展很是迅速的時代,不只手機已經稱爲了生活必需品,並且平板也變得愈來愈普及。平板和手機最大的區別就在於屏幕的大小:通常手機屏幕的大小在 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 佈局。