活動嵌入

在兩個活動 (或同一活動的兩個例項) 之間,活動嵌入功能會分割應用程式的工作視窗,藉此在大螢幕裝置上將應用程式最佳化。

圖 1:設定應用程式並排顯示活動。

如果應用程式內含多項活動,活動嵌入功能可讓您: 可在平板電腦、折疊式裝置和 ChromeOS 裝置上提供更優質的使用者體驗。

使用活動嵌入功能無需重構程式碼。您可以建立 XML 設定檔或發出 Jetpack WindowManager API 呼叫,決定要以並排還是堆疊的方式顯示應用程式活動。

系統會自動持續為小螢幕提供支援。如果應用程式是在螢幕較小的裝置上,活動會疊放顯示。如果是大螢幕,活動會並列顯示。系統會判斷 顯示。 這通常代表交易 不會十分要求關聯語意

活動嵌入功能可支援裝置螢幕方向的變更,而且可順暢運作 在折疊式裝置上,隨著裝置折疊及展開 則發生的機率

搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置大多都支援活動嵌入功能。

分割工作視窗

活動嵌入會將應用程式工作視窗分割為兩個容器:主要容器與次要容器。除了保有從主要活動中啟動的活動以外,這些容器也會保有從容器現有的其他活動中啟動的活動。

啟動後,活動會堆疊在次要容器中,次要容器則堆放在螢幕較小的主要容器上方,因此活動堆疊和返回瀏覽會與已內建在應用程式中的活動順序保持一致。

活動嵌入功能可讓您以多種方式顯示活動。您的應用程式可以同時啟動兩個並排顯示的活動,藉此分割工作視窗:

圖 2 兩項活動並排顯示。

或者,如果活動會占用整個工作視窗,您可以在側邊啟動新活動,替原活動建立分割畫面:

圖 3. 活動 A 在側邊啟動活動 B。

已分割及共用工作視窗中的活動,可以透過以下幾種方式啟動其他活動:

  • 在側邊的其他活動之上:

    圖 4. 活動 A 在活動 B 旁邊啟動活動 C。
  • 在側邊啟動一個活動,並使分割視窗側移,以隱藏先前的主要活動:

    圖 5. 活動 B 在側邊啟動活動 C,並將
  • 從原活動上方啟動活動;也就是兩個活動位在相同的活動堆疊中:

    圖 6. 活動 B 啟動了活動 C,並且沒有額外的意圖旗標。
  • 在同一個工作中以全視窗模式啟動活動:

    圖 7. 活動 A 或活動 B 啟動了活動 C,其中填入 工作視窗

返回瀏覽

不同類型的應用程式可以在分割工作視窗狀態中加入不同的返回瀏覽規則,具體視活動之間的相依性或使用者觸發返回事件的方式而定,例如:

  • 一起執行:如果活動之間具有關聯性,且不應單獨顯示,則可以將返回瀏覽設定為完成這兩個活動。
  • 單獨執行:如果活動完全獨立,則一個活動的返回瀏覽不會影響工作視窗中另一個活動的狀態。

使用按鈕時,系統會將返回事件傳送至上一個聚焦的活動 導覽。

手勢操作:

  • Android 14 (API 級別 34) 以下版本:系統會將返回事件傳送至 手勢發生的活動。使用者從 YouTube 首頁左側 系統會將返回事件傳送至左側活動 「分割」視窗的窗格使用者從應用程式右側 畫面,返回事件就會傳送至右側窗格中的活動。

  • Android 15 (API 級別 35) 以上版本

    • 在處理來自同一應用程式的多項活動時,手勢 完成上方活動 (不論滑動方向為何),提供 提供更一致的體驗

    • 在涉及不同應用程式 (重疊) 的情況下,返回事件會導向焦點中的最後一個活動,與按鈕導覽的行為一致。

多窗格版面配置

Jetpack WindowManager 可讓您建構活動嵌入多窗格 搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置上的版面配置 某些裝置搭載舊版平台。現有以多個活動為基礎的應用程式 (而非使用片段或檢視畫面的版面配置,例如:SlidingPaneLayout) 可改善大螢幕的使用者體驗,而無需重構原始碼。

常見的例子是清單/詳細資料分割畫面。為了確保高品質 系統會啟動清單活動,接著應用程式 系統會隨即啟動詳細資料活動。等到 系統會繪製活動,然後一併顯示對使用者而言,這兩個活動會做為一個活動啟動。

圖 8. 兩個活動在多窗格版面配置中同時啟動。

分割屬性

您可以指定工作視窗在分割容器間的比例,以及容器彼此間的版面配置方式。

針對 XML 設定檔中定義的規則,請設定下列屬性:

  • splitRatio:設定容器比例。這個值是開區間內 (0.0、1.0) 的浮點數。
  • splitLayoutDirection:指定分割容器彼此間的版面配置方式。相關的值包括:
    • ltr:從左到右
    • rtl:從右到左
    • localeltrrtl,取決於語言代碼設定

如需範例,請參閱 XML 設定一節。

針對使用 WindowManager API 建立的規則,建立 SplitAttributes 物件使用 SplitAttributes.Builder,並呼叫下列建構工具 方法:

如需範例,請參閱「WindowManager API」一節。

圖 9。 兩個活動的分割畫面,採用從左到右的版面配置,但分割比例不同。

預留位置

預留位置活動是空白的次級活動,會占用活動分割區域。最終會被替換為含有內容的其他活動。舉例來說,在清單/詳細資料的版面配置中,預留位置活動可能會占用活動分割中次級的一側,直到選取了清單中的一個項目,具有該選取清單項目內容的活動將該預留位置取代為止。

根據預設,只有在空間足以進行活動分割時,系統才會顯示預留位置。在顯示大小時,預留位置會自動結束 改變寬度或高度太小,無法顯示分割畫面。如果空間允許 系統會重新啟動預留位置,並讓狀態重新初始化。

圖 10. 摺疊式裝置的摺疊及展開。預留位置活動完成,並在顯示大小變更時重新產生。

不過,SplitPlaceholderRulestickyPlaceholder 屬性或 SplitPlaceholder.BuildersetSticky() 方法可能會覆寫預設行為。如果屬性或方法指定 true 的值, 系統會在發生此情況時,在工作視窗中將預留位置顯示為最上方的活動 螢幕縮小為雙窗格顯示畫面 (如需範例,請參閱「分割設定」一節)。

圖 11. 摺疊式裝置的摺疊及展開。預留位置活動是固定的。

視窗大小變化

裝置設定變更後,工作視窗寬度會減少 夠大,可支援多窗格的版面配置 (例如大螢幕摺疊式裝置時) 裝置摺疊時從平板電腦大小到手機大小,或者, 多視窗模式), 工作視窗會堆疊在主要窗格中的活動上方。

只有在分割畫面的顯示寬度足夠時,才會顯示預留位置活動。在較小的螢幕上,預留位置會自動關閉。當 顯示區域變得夠大,系統會重新建立預留位置。(請參閱 Placeholders (預留位置)。

而活動堆疊是可能的,因為 WindowManager 會以 Z 順序排列活動 會顯示在主要窗格中活動上方的次要窗格中。

次要窗格中的多個活動

活動 B 啟動了活動 C,其中沒有額外的意圖旗標:

包含活動 A、B 和 C 的活動分割,其中 C 堆疊在 B 上方。

結果導致同一工作中的活動會按照以下 Z 順序排序:

次級活動堆疊,包含堆疊在 B 上方的活動 C。次級堆疊堆疊在含有活動 A 的主要活動堆疊之上。

因此,在較小的工作視窗中,應用程式會縮減為單一活動 堆疊頂端的 C:

只顯示活動 C 的小型視窗。

在較小視窗中返回瀏覽可以瀏覽堆疊在彼此之上的活動。

如果工作視窗設定還原到較大尺寸,能夠容納多個窗格,活動就會再次並排顯示。

堆疊分割

活動 B 則會在側邊啟動活動 C,並將分割移至兩側:

工作視窗顯示活動 A 和 B,接著是活動 B 和 C。

結果導致同一工作中的活動會按照以下 Z 順序排序:

位於單一堆疊的活動 A、B 和 C。活動依以下順序從上至下堆疊:C、B、A。

在較小的工作視窗中,應用程式會縮減為單一活動,且 C 開啟 上:

只顯示活動 C 的小型視窗。

固定直向螢幕方向

android:screenOrientation 資訊清單設定可讓應用程式限制 直向或橫向的活動改善使用者體驗 在平板電腦、折疊式裝置等大螢幕裝置上,裝置製造商 (OEM) 可以忽略螢幕方向要求,並針對直向的應用程式加上黑邊 螢幕方向 (橫向) 或橫向 (縱向) 顯示。

圖 12.採用上下黑邊的活動:在橫向裝置上固定為直向 (左側),而直向裝置上則固定為橫向 (右側)。

同樣地,啟用活動嵌入功能時,原始設備製造商 (OEM) 便可針對大螢幕 (寬度 ≥ 600dp) 的橫向螢幕方向自訂裝置的上下黑邊固定直向活動。固定直向活動啟動第二個活動時,裝置可以在雙窗格顯示畫面中並排顯示兩個活動。

圖 13。 固定直向活動 A 在活動 B 旁啟動。

一律加入 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED 屬性加入應用程式資訊清單檔案中,告知裝置您的應用程式支援哪些裝置 活動嵌入功能 (請參閱分割設定 部分)。原始設備製造商 (OEM) 客製化裝置即可判斷是否要加上黑邊 固定直向活動。

分割設定

活動分割是由分割規則所設定。您可在 XML 中定義分割規則 或製作 Jetpack WindowManager API 呼叫。

無論是哪種情況,應用程式都必須存取 WindowManager 程式庫,且必須告知 應用程式已實作活動嵌入功能的系統。

請完成下列步驟:

  1. 將最新的 WindowManager 程式庫依附元件新增至應用程式模組層級的 build.gradle 檔案,例如:

    implementation 'androidx.window:window:1.1.0-beta02'

    WindowManager 程式庫提供了活動所需的所有元件 和嵌入的內容

  2. 向系統說明您的應用程式已實作活動嵌入功能。

    在應用程式資訊清單檔案的 <application> 元素中新增 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED 屬性,然後將值設為 true,例如:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    在 WindowManager 1.1.0-alpha06 以上版本中,活動嵌入功能的分割作業 不具有停用狀態,除非該屬性在資訊清單中加入並設為 true。

    此外,裝置製造商也會利用這項設定啟用自訂功能 支援活動嵌入功能的應用程式。舉例來說,如果螢幕處於橫向模式,裝置可以在僅限直向顯示的活動兩側加上黑邊,並在第二項活動開始時調整第一項活動的位置,轉換為雙窗格的版面配置 (請參閱「固定直向螢幕方向」)。

XML 設定

如要建立以 XML 為依據的活動嵌入功能實作,請完成 步驟如下:

  1. 建立可執行下列操作的 XML 資源檔案:

    • 定義共用分割作業的活動
    • 設定分割選項
    • 在沒有可用內容時,為分割作業的次要容器建立預留位置
    • 指定一律不得納入分割作業的活動

    例如:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. 建立 Initializer。

    WindowManager RuleController 元件會剖析 XML 並且向系統提供規則。Jetpack Startup 程式庫 Initializer 會將 XML 檔案提供給 在應用程式啟動時執行 RuleController,讓規則在任何情況下生效 活動開始時,

    如要建立 Initializer,請執行下列步驟:

    1. 將最新的 Jetpack Startup 程式庫依附元件新增至模組層級 build.gradle 檔案,例如:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. 建立實作 Initializer 介面的類別。

      Initializer 會透過以下方式,向 RuleController 提供分割規則: 傳遞 XML 設定檔 (main_split_config.xml) 的 ID 加入 RuleController.parseRules() 方法

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
          override fun create(context: Context): RuleController {
              return RuleController.getInstance(context).apply {
                  setRules(RuleController.parseRules(context, R.xml.main_split_config))
              }
          }
      
          override fun dependencies(): List<Class<out Initializer<*>>> {
              return emptyList()
          }
      }

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
           @NonNull
           @Override
           public RuleController create(@NonNull Context context) {
               RuleController ruleController = RuleController.getInstance(context);
               ruleController.setRules(
                   RuleController.parseRules(context, R.xml.main_split_config)
               );
               return ruleController;
           }
      
           @NonNull
           @Override
           public List<Class<? extends Initializer<?>>> dependencies() {
               return Collections.emptyList();
           }
      }
  3. 建立規則定義的內容供應器。

    在應用程式資訊清單檔案中,新增 androidx.startup.InitializationProvider 做為 <provider>。附上 RuleController 初始化器,SplitInitializer

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    在呼叫應用程式的 onCreate() 方法之前,InitializationProvider 會探索並初始化 SplitInitializer。因此,分割規則 。

WindowManager API

您可以發出幾個 API 呼叫,透過程式輔助的方式實作活動嵌入功能。在以下子類別的 onCreate() 方法中發出呼叫: Application,確保規則在任何活動前生效 發布。

如要透過程式輔助的方式建立活動分割作業,請完成下列步驟:

  1. 建立分割規則:

    1. 建立 SplitPairFilter,用於識別共用分割畫面的活動:

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
    2. 在篩選器組合中新增篩選器:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. 建立分割畫面的版面配置屬性:

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder 會建立一個包含版面配置屬性的物件:

      • setSplitType():定義可用的顯示區域如何分配至各個活動容器。比率分割類型會指定分配給主要容器的可用顯示區域比例;次要容器會占用可用顯示區域的剩餘部分。
      • setLayoutDirection():指定活動容器的方式 這些容器會先透過相對的元件配置,然後再先配置主要容器。
    4. 建構 SplitPairRule

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      SplitPairRule.Builder 會建立並設定規則:

      • filterSet:包含分割畫面組篩選器,可識別共用分割畫面的活動,決定套用規則的時機。
      • setDefaultSplitAttributes():將版面配置屬性套用至 規則。
      • setMinWidthDp():設定最小顯示寬度 ( 適用於分割作業的密度獨立像素、dp。
      • setMinSmallestWidthDp():設定無論裝置螢幕方向為何,只要兩個顯示畫面尺寸中較小者達到這個下限 (以 dp 為單位),就必須啟用分割作業。
      • setMaxAspectRatioInPortrait():設定顯示活動分割畫面的直向顯示比例上限 (高度:寬度)。如果直向的顯示比例超過顯示比例上限,那麼無論螢幕寬度為何,系統都會停用分割作業。注意:預設值為 1.4,因此活動會占據大多數平板電腦的整個直向工作視窗。另請參閱 SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape()。預設值 橫向為 ALWAYS_ALLOW
      • setFinishPrimaryWithSecondary():設定如何完成所有項目 次要容器中的活動會影響 主要容器中執行NEVER 表示系統不應結束 次要活動中的所有活動 容器完成 (請參閱「完成活動」)。
      • setFinishSecondaryWithPrimary():設定若完成主要容器中的所有活動,對次要容器中的活動有何影響。ALWAYS 表示在主要容器中的所有活動完成時,系統應一律完成次要容器中的活動 (請參閱「完成活動」)。
      • setClearTop():指定是否要將 如果次要容器在 中啟動新活動 false 值會指定新活動堆疊在次要容器中的現有活動之上。
    5. 取得 WindowManager RuleController 的單例模式例項,然後新增規則:

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. 在為次要容器建立預留位置時 內容無法播放:

    1. 建立 ActivityFilter,用來識別要套用哪一項活動的活動 預留位置會共用工作視窗分割畫面:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. 在篩選器組合中新增篩選器:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. 建立 SplitPlaceholderRule

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      SplitPlaceholderRule.Builder 會建立並設定規則:

      • placeholderActivityFilterSet:包含活動篩選器,可識別與預留位置活動相關聯的活動,決定套用規則的時機。
      • Intent:指定預留位置活動的啟動作業。
      • setDefaultSplitAttributes():將版面配置屬性套用至規則。
      • setMinWidthDp():設定可允許分割作業的最小螢幕寬度 (以密度獨立像素 dp 為單位)。
      • setMinSmallestWidthDp():設定無論裝置螢幕方向為何,只要兩個顯示畫面尺寸中較小者達到這個下限 (以 dp 為單位),就必須允許分割作業。
      • setMaxAspectRatioInPortrait(): 設定直向顯示的最大顯示比例 (高度:寬度) 顯示活動分割畫面的方向。注意:預設值為 1.4,因此活動會填滿大多數平板電腦的直向工作視窗。另請參閱 SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape()。橫向的預設值是 ALWAYS_ALLOW
      • setFinishPrimaryWithPlaceholder(): 設定完成預留位置活動對活動的影響 主要容器中的物件ALWAYS 表示在預留位置完成時,系統應一律完成主要容器中的活動 (請參閱「完成活動」)。
      • setSticky():決定一旦預留位置首次出現在寬度下限充足的分割畫面時,預留位置活動是否要顯示在小螢幕上的活動堆疊頂端。
    4. 將規則新增至 WindowManager RuleController

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. 指定一律不得納入分割作業的活動:

    1. 建立 ActivityFilter,用於識別應當的活動 一律佔用整個工作顯示區域:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. 在篩選器組合中新增篩選器:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. 建立 ActivityRule

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder 會建立並設定規則:

      • expandedActivityFilterSet:包含的活動篩選器 識別您有興趣的活動,決定套用規則的時機 從分割作業中排除
      • setAlwaysExpand():指定活動是否應填滿整個工作視窗。
    4. 將規則新增至 WindowManager RuleController

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

跨應用程式嵌入

在 Android 13 (API 級別 33) 以上版本中,應用程式可以嵌入其他應用程式的活動。跨應用程式 (或跨 UID) 活動嵌入功能會在視覺上整合多個 Android 應用程式的活動。系統會在螢幕上並排或上下顯示主機應用程式的活動與其他應用程式的嵌入活動,就像單一應用程式活動嵌入。

舉例來說,「設定」應用程式可嵌入桌布選取器活動 WallpaperPicker 應用程式:

圖 14.「設定」應用程式 (左側選單) 和做為內嵌活動的桌布選取器 (右側)。

信任模式

嵌入其他應用程式活動的主機程序能夠重新定義嵌入活動的呈現方式,包括尺寸、位置、裁剪和透明度。惡意主機可以使用這項功能誤導使用者,並發起點閱綁架或其他 UI 阻礙攻擊。

如要防止濫用跨應用程式活動嵌入功能,Android 要求應用程式選擇允許嵌入其活動。應用程式可以將主機指定為信任的主機 或不受信任的內容

信任的託管

如要允許其他應用程式嵌入並完整控制您應用程式的活動呈現方式,請在應用程式的資訊清單檔案 <activity><application> 元素的 android:knownActivityEmbeddingCerts 屬性中指定主機應用程式的 SHA-256 憑證。

android:knownActivityEmbeddingCerts 的值設為字串:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

或如要指定多個憑證,則可使用字串陣列:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

參照如下所示的資源:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

應用程式擁有者可以執行 Gradle,以取得 SHA 憑證摘要 signingReport工作。憑證摘要是沒有 SHA-256 指紋 分隔冒號詳情請參閱「執行簽署報告」和 驗證用戶端

不信任的託管

如要允許任何應用程式嵌入您應用程式的活動並控制其呈現方式,請在應用程式資訊清單的 <activity><application> 元素中指定 android:allowUntrustedActivityEmbedding 屬性,例如:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

該屬性的預設值為 false,藉此防止跨應用程式活動 和嵌入的內容

自訂驗證

為了減輕嵌入不信任活動嵌入的風險,請建立自訂 驗證主機身分的驗證機制如果您知道主機憑證,請使用 androidx.security.app.authenticator 程式庫進行驗證。如果主機在嵌入您的活動後進行驗證,您就可以 顯示實際內容如果不是,您可以通知使用者這項操作 並封鎖內容

使用 Jetpack WindowManager 程式庫中的 ActivityEmbeddingController#isActivityEmbedded() 方法,檢查主機是否正在嵌入您的活動,例如:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

最小尺寸限制

Android 系統會套用應用程式中指定的最小高度和寬度 嵌入活動資訊清單 <layout> 元素。如果應用程式 未指定最小高度和寬度,系統會套用系統預設值 (sw220dp)。

主機嘗試將嵌入容器的大小調整為小於 嵌入的容器則會展開,佔用整個工作界限

<activity-alias>

如要搭配 <activity-alias> 元素使用信任或不信任的活動嵌入,android:knownActivityEmbeddingCertsandroid:allowUntrustedActivityEmbedding 必須套用至目標活動 (而非別名)。驗證系統伺服器上安全性的政策是 是根據在目標上設定的旗標,而非別名

託管應用程式

主機應用程式實作跨應用程式活動嵌入的方式,就跟實作單一應用程式活動嵌入的方式相同。SplitPairRuleSplitPairFilterActivityRuleActivityFilter 物件 指定嵌入活動和工作視窗分割。已定義分割規則 在 XML 中靜態處理,或使用 Jetpack 在執行階段中 WindowManager API 呼叫。

如果主機應用程式嘗試嵌入尚未選擇加入的活動 跨應用程式嵌入,活動會佔用整個工作界限。因此,主機應用程式必須知道目標活動是否允許跨應用程式嵌入。

如果嵌入的活動在同一工作中啟動新活動,而新的活動 活動未選擇加入跨應用程式嵌入,該活動 整個工作界限,而非疊加嵌入容器中的活動。

只要活動在同一工作中啟動,代管應用程式就可以不受限制地嵌入自己的活動。

分割範例

從整個視窗分割

圖 15。 活動 A 在側邊啟動活動 B。

不需重構。您可以在靜態時或在執行階段定義分割的設定,然後呼叫 Context#startActivity(),且不需任何其他參數。

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

預設分割

如果應用程式的到達網頁設計為在大型螢幕上分割為兩個容器,則同時建立並顯示兩個活動便會給使用者帶來最佳體驗。不過 提供給分割的次要容器使用,直到使用者與事件互動為止 主要容器中的活動 (例如,使用者選取某個項目 。預留位置活動可以填滿空白,直到內容為止 可以顯示在分割作業的次要容器中 (請參閱 Placeholders (預留位置) 部分)。

圖 16。 同時開啟兩個活動來建立分割。 一個活動是預留位置。

如要建立含有預留位置的分割,請建立預留位置,並將其與 主要活動:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

當應用程式收到意圖時,目標活動可能會顯示為活動分割視窗的次要部分;例如,要求顯示包含某個清單項目資訊的詳細資料畫面。在小螢幕上,細節 會顯示在整個工作視窗中;顯示在大型裝置上。

圖 17。 深層連結的詳細資料活動單獨顯示在小螢幕上,不過在大螢幕上則與清單活動一起顯示。

啟動要求應轉送至主要活動,而目標詳細資料活動則應在分割視窗中啟動。系統會自動選擇 正確的方式 (堆疊或並排),視可用空間而定 顯示寬度

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

深層連結目的地可能會是唯一應可參與的活動 使用者,但建議您避免關閉 詳細資料活動,並且僅留下主要活動:

並列顯示清單活動及詳細資料活動的大螢幕。返回瀏覽無法關閉詳細資料活動,而將清單活動留在螢幕上。

只顯示詳細活動的小螢幕。返回瀏覽無法關閉詳細資料活動而顯示清單活動。

您可使用 finishPrimaryWithSecondary 屬性同時完成這兩項活動:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

請參閱「設定屬性」一節。

分割容器中的多項活動

在分割容器中堆疊多個活動,可讓使用者存取深度學習內容。舉例來說,使用清單/詳細資料分割畫面時,使用者可能需要前往子詳細資料,但主要活動保持不變:

圖 18。 在工作視窗的次要窗格中開啟的活動。

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

子細項活動放在詳細資料活動之上,將其隱藏:

接著,使用者只要依堆疊返回瀏覽,即可回到之前的詳細資料層級:

圖 19。 活動已從堆疊頂端移除。

根據預設,從同一個次要容器中啟動的活動會彼此堆疊。在有效的分割視窗中,從主要容器啟動的活動,最終也會落在活動堆疊頂部的次要容器中。

新工作中的活動

當分割工作視窗中的活動在新工作中啟動活動時,新工作會與包含分割視窗的工作分離,並以完整的視窗顯示。近期存取的畫面會顯示兩項工作:分割中的工作和新工作。

圖 20. 在活動 B 的新工作中啟動活動 C。

活動替換

您可以替換次要容器堆疊中的活動;舉例來說,如果主要活動用於頂層導覽,則次要活動就用於所選的目的地。從頂層導覽所做的每個選項,都必須在次要容器中啟動新活動,並移除之前在該位置的活動。

圖 21. 主要窗格中的頂層瀏覽活動會取代次要窗格中的目的地活動。

如果導覽選項變更時,應用程式未完成次要容器中的活動,則在收合分割視窗時 (當裝置收合時),返回瀏覽可能會令人感到困惑。舉例來說,如果主窗格有一個選單,而次要窗格中堆疊了螢幕 A 和 B,則當使用者摺疊手機時,B 位於 A 頂部,而 A 則位於選單頂部。當使用者 從 B 返回瀏覽,此時會顯示 A,而非選單。

這種情況下,就必須從返回堆疊中移除螢幕 A。

根據預設,在現有分割視窗上方的新容器側邊啟動時,系統會將新的次要容器置於上方,並在返回堆疊中保留舊容器。您可以設定分割,利用 clearTop 清除之前的次要容器,並照常啟動新活動。

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

您也可以使用相同的次要活動,也可以使用主要 (選單) 中的 活動會傳送解析為同一例項但觸發狀態的新意圖 或次要容器的使用者介面更新

多重分割

應用程式可以在側邊啟動其他活動,提供多層級的深度瀏覽功能。

當次要容器中的活動在側邊啟動新活動時, 新的分割方式會在現有分割上方建立

圖 22.活動 B 在側邊啟動活動 C。

返回堆疊包含了之前開啟的所有活動,因此使用者可以 完成後,請前往 A/B 分割畫面

堆疊中的活動 A、B 和 C。活動依以下順序從上至下堆疊:C、B、A。

如要建立新的分割,請從現有的次要容器中在側邊啟動新活動。宣告 A/B 和 B/C 分割的設定,再從 B 正常啟動活動 C:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

回應分割狀態變更

應用程式中的不同活動可能會有執行相同功能的 UI 元素,例如開啟一個具有帳戶設定的視窗控制項。

圖 23.具有相同功能的 UI 元素的不同活動。

如果分割的兩個活動含有共同的 UI 元素,那麼在兩個活動中呈現元素,可能會顯得多餘且可能造成混淆。

圖 24.活動分割中重複的 UI 元素。

如要瞭解活動是否位於分割畫面,請查看 SplitController.splitInfoList 流程,或透過 SplitControllerCallbackAdapter 註冊事件監聽器來監聽分割狀態的變化。然後視需要調整使用者介面:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

協同程式可在任何生命週期狀態下啟動,但通常會在 STARTED 狀態,以便節省資源 (請參閱「將 Kotlin 協同程式用於 生命週期感知元件)。

回呼可針對所有生命週期狀態進行,包括在活動停止時。事件監聽器應在 onStart() 中註冊,且未註冊 位置:onStop()

完整視窗互動畫面

有些活動會阻止使用者與應用程式互動,直到執行指定的操作為止;例如登入的螢幕活動、政策確認畫面或錯誤訊息。分割畫面中應防止出現模式活動。

您可以使用展開的設定,強制將活動一律填入工作視窗:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

完成活動

使用者可以從邊緣滑動,完成任一側分割畫面的活動 螢幕:

圖 25.完成活動 B 的滑動手勢。
圖 26.完成活動 A 的滑動手勢。

如果將裝置設為使用返回按鈕,而非手勢操作,系統會將輸入內容傳送至焦點活動,也就是上次觸碰或啟動的活動。

完成容器中的所有活動會對相對的容器造成什麼影響,須視分割設定而定。

設定屬性

您可以指定分割配對規則屬性,以設定在分割畫面中一邊完成所有活動如何影響另一邊的活動。屬性包括:

  • window:finishPrimaryWithSecondary - 如何完成 次要容器會影響主要容器中的活動
  • window:finishSecondaryWithPrimary - 如何完成 主要容器會影響次要容器中的活動

屬性可能的值包括:

  • always:一律完成關聯容器中的活動
  • never:絕不完成關聯容器中的活動
  • adjacent:在發生以下情況時完成關聯容器中的活動 兩個容器相鄰顯示,但不適用於

例如:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

預設設定

在分割作業中,如果單一容器內的所有活動皆完成後,剩餘的容器會占滿整個視窗:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

包含活動 A 及 B 的分割。已完成 A,留下 B 佔滿整個視窗。

包含活動 A 及 B 的分割。已完成 B,留下 A 佔滿整個視窗。

一起完成多項活動

次要容器中的所有活動完成後,自動完成主要容器中的活動:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

包含活動 A 及 B 的分割。B 已完成,也因此完成了 A,留下工作視窗呈現空白。

包含活動 A 及 B 的分割。完成 A,工作視窗中單獨留下 B。

主要容器中的所有活動完成後,自動完成次要容器中的活動:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

包含活動 A 及 B 的分割。A 完成,也因此完成了 B,留下工作視窗呈現空白。

包含活動 A 及 B 的分割。完成 B,工作視窗中留下 A。

主要或主要中的所有活動時,一起完成多項活動 次要容器完成:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

包含活動 A 及 B 的分割。A 完成,也因此完成了 B,留下工作視窗呈現空白。

包含活動 A 及 B 的分割。B 已完成,也因此完成了 A,留下工作視窗呈現空白。

在容器中完成多項活動

如果分割容器中有多個活動堆疊,完成堆疊底部的活動並不會自動完成上方的活動。

舉例來說,如果有兩個活動位於次要容器中,C 位於 B 之上:

其中活動 C 堆放在 B 上方的次要活動堆疊在含有活動 A 的主要活動堆疊之上。

而分割的設定是依照活動 A 和 B 的設定所定義:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

完成上方的活動後,分割繼續保留。

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。C 完成後,在活動分割中留下 A 和 B。

完成次要容器的底部 (根) 活動不會移除 上面的活動所以仍保留分割畫面

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。B 完成後,在活動分割中留下 A 和 C。

與完成活動相關的其他規則,例如完成 也會執行具有主要次要資料的次要活動:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。完成 A 後,接著完成 B 和 C。

當分割設定為同時完成主要與次要容器時:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。C 完成後,在活動分割中留下 A 和 B。

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。B 完成後,在活動分割中留下 A 和 C。

分割視窗,其中活動 A 在主要容器中,活動 B 和 C 在次要容器中,並且 C 在 B 上方。A 完成後,也結束 B 和 C。

在執行階段變更分割屬性

無法變更目前使用中且可見的分割屬性。變更 分割規則會影響其他活動發布作業和新容器,但不會影響 現有的和有效分割

如要變更現有分割的屬性,請完成分割視窗的側邊活動或活動,然後使用新設定再次啟動側邊視窗。

動態分割屬性

Android 15 (API 級別 35) 以上版本由 Jetpack WindowManager 1.4 以上版本支援,提供可讓活動嵌入分割作業可設定的動態功能,包括:

  • 窗格展開:互動式的可拖曳分隔線,可讓使用者 調整分割簡報中的窗格大小。
  • 固定活動:使用者可以固定單一容器中的內容, 將容器中的導覽機制與另一個容器中的導覽區隔開來。
  • 調暗全螢幕對話方塊:顯示對話方塊時,應用程式可以指定 無論是調暗整個工作視窗 還是只調暗開啟 對話方塊

展開窗格

「窗格展開」功能可讓使用者調整螢幕空間所分配到的 這兩個活動

如要自訂視窗分隔符的外觀,並設定分隔符的可拖曳範圍,請按照下列步驟操作:

  1. 建立 DividerAttributes 的例項

  2. 自訂分隔線屬性:

    • color可拖曳窗格分隔符的顏色。

    • widthDp可拖曳的窗格分隔符寬度。將其設為 WIDTH_SYSTEM_DEFAULT,讓系統決定分隔線的寬度。

    • 拖曳範圍:兩個窗格的最小畫面百分比 佔用的介於 0.33 至 0.66 之間。設為 DRAG_RANGE_SYSTEM_DEFAULT 可讓系統決定拖曳動作 範圍。

Kotlin

val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    )
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

Java

SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      new DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(ContextCompat.getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

活動固定

活動固定功能可讓使用者固定其中一個分割視窗,這樣當使用者在其他視窗中瀏覽時,活動就會維持原樣。活動量 固定功能可讓你享有更優異的多工處理體驗。

如要在應用程式中啟用活動固定功能,請按照下列步驟操作:

  1. 在要固定的活動的版面配置檔案中加入按鈕, 例如,清單/詳細資料版面配置的詳細資料活動:

    <androidx.constraintlayout.widget.ConstraintLayout
     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:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. 在活動的 onCreate() 方法中,為 按鈕:

    Kotlin

    pinButton = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) => {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule);
    });

螢幕調暗

活動通常會將畫面調暗,以吸引使用者注意對話方塊。於 活動嵌入功能,雙窗格螢幕的兩個窗格都應調暗,而不是 只有開啟對話方塊的活動窗格 (如果是統一的使用者介面) 無須專人管理

使用 WindowManager 1.4 以上版本時,根據預設,當 對話方塊會隨即開啟 (請參閱 EmbeddingConfiguration.DimAreaBehavior.ON_TASK)。

如要只將開啟對話方塊的活動容器調暗,請使用 EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK

將活動從分割中擷取至完整視窗

建立新的設定,將側邊活動顯示為完整視窗,然後 透過意圖重新啟動活動,該意圖會解析相同的例項。

在執行階段確認是否支援分割

Android 12L (API 級別 32) 以上版本支援活動嵌入功能, 也適用於部分搭載舊版平台的裝置如要在執行階段檢查這項功能的可用性,請使用 SplitController.splitSupportStatus 屬性或 SplitController.getSplitSupportStatus() 方法:

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

如果不支援分割,活動會從活動上方啟動 (按照非活動嵌入模型)

防止系統覆寫

Android 裝置的製造商 (原始設備製造商或 OEM) 可以將活動嵌入做為裝置系統功能實作。系統會針對多活動應用程式指定分割規則,並覆寫應用程式的視窗化行為。系統覆寫功能會強制多活動應用程式進入系統定義的活動嵌入模式。

系統活動嵌入可透過多窗格版面配置 (例如 list-detail) 增強應用程式的呈現方式,而不需要變更應用程式。然而,系統的活動嵌入也可能導致錯誤的應用程式版面配置、錯誤,或與應用程式實作的活動嵌入發生衝突。

應用程式可以透過在應用程式資訊清單檔案中設定屬性,防止或允許系統嵌入活動,例如:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

屬性名稱是在 Jetpack WindowManager WindowProperties 物件中定義。如果您的應用程式實作活動嵌入功能,請將值設為 false;或 防止系統套用活動嵌入 你的應用程式要遵守哪些規則將值設為 true 即可允許系統套用 系統定義的活動嵌入應用程式

限制及注意事項

  • 只有執行工作的代管應用程式(確認為工作根活動的擁有者),才能在工作中整理並嵌入其他活動。如果可支援嵌入和分割的活動執行於屬於其他應用程式的工作,那麼這些活動將無法使用嵌入和分割。
  • 只能將活動安排在一項工作中。在新工作中啟動活動時,系統一律將其放至現有分割外的新展開視窗。
  • 只有同一個程序中的活動可以被歸類放在同一個分割中。 SplitInfo 回呼只會回報屬於相同活動的活動 因為無法得知不同活動中的活動 作業。
  • 只有在註冊規則後啟動的活動,才能套用每個配對或單獨活動規則。目前無法更新現有的分割項目或其視覺屬性。
  • 分割配對篩選器設定必須符合在完整啟動活動後的使用意圖。應用程式程序開始新活動後會進行比對,因此,在使用隱含意圖的情況下,可能無法得知之後在系統程序中解析的元件名稱。如果在啟動時不知道元件名稱, 請改用萬用字元 (「*/*」),並根據條件執行篩選 意圖動作
  • 目前無法在容器之間移動活動,也無法在建立分割視窗後移入或移出活動。只有在啟動具有匹配規則的新活動時,WindowManager 程式庫才會建立分割,並且系統會在分割容器中的最後一個活動完成後刪除這些分割。
  • 變更設定時,系統會重新啟動活動,因此當建立或移除分割並變更活動範圍時,活動會徹底刪除上一個例項,並建立新的例項。因此,應用程式開發人員在從生命週期回呼中啟動新活動時應謹慎處理。
  • 裝置必須包含視窗擴充功能介面,才能支援活動 和嵌入的內容搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置幾乎都含有該介面。不過,一些大螢幕裝置 無法執行多項活動,不包含視窗 擴充功能介面。如果大螢幕裝置不支援多視窗模式,可能就不支援活動嵌入功能。

其他資源