アプリを折りたたみ対応にする

開いた状態の大型ディスプレイと独自の折りたたみ状態により、Google Pixel Tablet で 折りたたみ式デバイスにも対応しています。アプリを折りたたみ対応にするには、Jetpack WindowManager を使用します。 ライブラリ: 折りたたみ式デバイスのウィンドウ機能用の API サーフェスを提供します。 ひび割れやヒンジといった特性ですアプリが折りたたみ対応の場合、レイアウトを適応させることができる 重要なコンテンツを折り目またはヒンジの部分に配置しないようにして、折り目を使用する ヒンジを自然のセパレータとして使います

デバイスがテーブルトップ形状やブック形状などの構成をサポートしているかどうかを把握することで、さまざまなレイアウトのサポートや特定の機能の提供に関する意思決定を導くことができます。

ウィンドウ情報

Jetpack WindowManager の WindowInfoTracker インターフェースがウィンドウを公開する あります。インターフェースの windowLayoutInfo() メソッドが 折りたたみ式デバイスについてアプリに通知する WindowLayoutInfo データのストリーム 折りたたまれた状態です。WindowInfoTracker#getOrCreate() メソッドは、 WindowInfoTracker のインスタンス。

WindowManager は、以下を使用した WindowLayoutInfo データの収集をサポートしています。 Kotlin のフローと Java コールバック。

Kotlin Flow

WindowLayoutInfo のデータ収集を開始および停止するには、再起動可能な ライフサイクル対応コルーチン。ここで、repeatOnLifecycle コードブロックは ライフサイクルが STARTED 以上のときに実行され、 ライフサイクルは STOPPED です。ライフサイクルが再び STARTED になると、自動的にコードブロックの実行が再開されます。次の例のコードブロックでは、 WindowLayoutInfo のデータが収集、使用されます。

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

Java コールバック

androidx.window:window-java 依存関係に含まれるコールバック互換性レイヤを使用すると、Kotlin Flow を使用せずに WindowLayoutInfo の更新を収集できます。このアーティファクトには、 WindowInfoTrackerCallbackAdapter クラス: WindowInfoTracker: コールバックの登録(および登録解除)をサポート WindowLayoutInfo の更新を受信します。次に例を示します。

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

RxJava のサポート

RxJava(バージョン 2 または 3)をすでに使用している場合は、Observable または Flowable を使用して、Kotlin Flow を使用せずに WindowLayoutInfo の更新を収集するアーティファクトを利用できます。

androidx.window:window-rxjava2 および androidx.window:window-rxjava3 の依存関係によって提供される互換性レイヤには、WindowInfoTracker#windowLayoutInfoFlowable() メソッドと WindowInfoTracker#windowLayoutInfoObservable() メソッドが含まれています。アプリはこれらを使用して WindowLayoutInfo の更新を受信できます。次に例を示します。

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

折りたたみ式ディスプレイの機能

Jetpack WindowManager の WindowLayoutInfo クラスは、ディスプレイ ウィンドウの機能を DisplayFeature 要素のリストとして利用できるようにします。

FoldingFeatureDisplayFeature の一種で、情報を提供する 次のような折りたたみ式ディスプレイについて学びました。

  • state: デバイスの折りたたみ状態(FLAT または HALF_OPENED

  • orientation: 折り目またはヒンジの向き(HORIZONTAL または VERTICAL

  • occlusionType: 折り目またはヒンジがディスプレイを隠しているかどうか(NONE または FULL

  • isSeparating: 折り目またはヒンジによって 2 つの論理ディスプレイ領域が作成されるかどうか。 true または false

HALF_OPENED 状態の折りたたみ式デバイスは、画面が 2 つのディスプレイ領域に分割されているため、常に isSeparating を true として報告します。また、isSeparating は デュアル スクリーン デバイスでは、アプリが両方の画面にまたがっている場合、常に true あります。

FoldingFeaturebounds プロパティ(DisplayFeature から継承) 折り目やヒンジなどの折りたたみ機能の境界四角形を表します。 この境界を使用して、折りたたみ機能からの相対位置に画面上の要素を配置できます。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }

        }
    }
}

Java

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

テーブルトップ形状

FoldingFeature オブジェクトに含まれる情報を使用して、アプリで次の操作を行うことができます。 スマートフォンを定着させるテーブルトップや 折りたたみ式画面が半分開いた状態です。

テーブルトップ モードでは、スマートフォンを手に持たずに簡単に操作できます。テーブルトップ形状はメディア視聴に最適で 写真の撮影、ビデオ通話を行えます。

図 1. テーブルトップ形状の動画プレーヤー アプリ。

FoldingFeature.StateFoldingFeature.Orientation を使用して決定します。 デバイスがテーブルトップ形状にあるかどうかを判別します。

Kotlin

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

デバイスがテーブルトップの形状になっていることを確認したら、それに応じてアプリのレイアウトを更新します。メディアアプリの場合は、通常、ハンズフリーで視聴できるように、スクロールせずに見える範囲に再生を配置し、そのすぐ下にコントロールと補足コンテンツを配置します。

Android 15(API レベル 35)以降では、同期 API を呼び出して、 デバイスがテーブルトップ形状に対応しているかどうかを 確認します。

この API は、デバイスでサポートされているポスチャーのリストを提供します。リストにテーブルトップの向きが含まれている場合は、その向きをサポートするようにアプリ レイアウトを分割し、テーブルトップとフルスクリーンのレイアウトについてアプリ UI で A/B テストを実行できます。

Kotlin

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

ブック形状

折りたたみ式ならではのもう 1 つの形状としてブック形状があります。ブック形状とは、デバイスが半分開き、ヒンジが垂直の状態です。ブック形状は電子書籍を読むのに最適です。あり 2 ページ レイアウトの大画面の折りたたみ式デバイスで、製本された本のように開いた状態 実際の本を読む経験が取り込まれます。

また、写真撮影に使用すれば、ハンズフリーで写真を撮る際に異なるアスペクト比でキャプチャすることもできます。

ブックモードの実装方法はテーブルトップ モードと同じです。「 唯一の違いは 折りたたみ機能の向きが :

Kotlin

fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

Java

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

ウィンドウ サイズの変更

アプリのディスプレイ領域は、デバイス設定の変更に伴って変化する可能性があります。これには、デバイスが折りたたまれたとき、広げられたとき、回転されたとき、マルチウィンドウ モードでウィンドウのサイズが変更されたときなどがあります。

Jetpack WindowManager の WindowMetricsCalculator クラスを使用すると、次のことができます。 現在のウィンドウ指標と最大ウィンドウ指標を取得します。API レベル 30 で導入されたプラットフォームの WindowMetrics と同様に、WindowManager の WindowMetrics はウィンドウ境界を提供しますが、API には API レベル 14 までとの下位互換性があります。

ウィンドウ サイズクラスを使用するをご覧ください。

参考情報

サンプル

  • Jetpack WindowManager: Jetpack の使用方法の例 WindowManager ライブラリ
  • Jetcaster: Compose によるテーブルトップ形状の実装

Codelab