◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-06-01中級

targetSdk 36 で edge-to-edge が必須になった壁紙アプリを Antigravity で移行した運用メモ

targetSdk を 36(Android 16)に上げた途端、壁紙アプリのプレビュー画面のツールバーがステータスバーに食い込みました。opt-out が効かなくなった edge-to-edge 強制対応を、Antigravity のエージェントで複数アプリ横断に進めた数週間の運用メモです。

Android 16edge-to-edgetargetSdk 36Antigravity386個人開発108

targetSdk を 36 に上げてビルドし、いつもの壁紙プレビュー画面を開いた瞬間、上部のツールバーがステータスバーの時計に重なっていました。これまで windowOptOutEdgeToEdgeEnforcement を true にして逃げていた箇所が、Android 16 では効かなくなっていたのです。

私は2014年から個人で iOS / Android のアプリを運用していて、壁紙や癒し系のアプリが中心です。累計5,000万ダウンロードと言うと数だけは大きく聞こえますが、その実態は「同じような構造のアプリを何本も並行で抱えている」という状態で、OS のこうした仕様変更が来るたびに、全アプリを一斉に直す作業が発生します。今回の edge-to-edge 強制は、まさにその典型でした。この記事は手順の解説というより、複数アプリを横断して直すときに Antigravity のエージェントをどこで使い、どこで自分の手で確かめたか、という運用の記録です。

なぜ「あとで」では済まないのか

Android 15(API 35)の時点で、targetSdk 35 のアプリはデフォルトで edge-to-edge になっていました。ただしこのときは windowOptOutEdgeToEdgeEnforcement を true にすれば従来の挙動に戻せたので、私は正直この opt-out に頼って先送りしていました。

Android 16(API 36)で状況が変わります。targetSdk 36 のアプリでは、この属性が deprecated かつ無効化され、edge-to-edge を拒否できなくなりました。挙動を正確に書くと、targetSdk 36 のアプリでも「Android 15 の実機で動いている間」は opt-out がまだ効きますが、「Android 16 の実機」では無効になります。つまり端末が更新されれば、こちらが何もしなくても全画面化が降ってくるということです。

先送りできないと判断した理由はもう一つあります。壁紙アプリのプレビュー画面は、本来 edge-to-edge と相性が良いのです。画像を画面の隅まで見せたいのに、システムバーの下に余白を作っていたのは、むしろ機会損失でした。「いつか対応する課題」ではなく「今やると体験が良くなる改善」だと捉え直したことで、ようやく手が動きました。

まず opt-out を全アプリから消す

最初の作業は、逃げ道だった windowOptOutEdgeToEdgeEnforcement を全部消すことです。ここで Antigravity の Agent Mode に「リポジトリ横断でこの属性を使っている箇所を列挙して」と頼みました。themes.xml だけでなく、一部のアプリでは Activity 個別のテーマで上書きしていたので、自分の記憶だけでは取りこぼすところでした。

<!-- 削除前: これに頼っていた -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
 
<!-- 削除後: 属性ごと取り除く -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
    <!-- edge-to-edge を受け入れる前提に切り替える -->
</style>

合わせて、android:statusBarColor と android:navigationBarColor も targetSdk 36 では deprecated です。色で塗って隠すという発想自体を捨て、バーは透過させてコンテンツを下に敷く、という方針に切り替えました。

insets を「受け取る側」に書き換える

opt-out を消すと、当然あちこちでコンテンツがシステムバーに潜り込みます。ここからが本題で、各画面が insets(システムバーの占有領域)を受け取って自分でパディングを当てる必要があります。

View ベースの古い Activity では、enableEdgeToEdge() を呼んだ上で、ルートビューに inset リスナーを付けるのが確実でした。

import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
 
class PreviewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_preview)
 
        // ツールバーだけは必ずシステムバーを避ける。
        // 壁紙そのものは全画面のまま、操作 UI に上下パディングを当てる。
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.toolbar)) { v, insets ->
            val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(v.paddingLeft, bars.top, v.paddingRight, v.paddingBottom)
            insets
        }
    }
}

ここでつまずいたのは、fitsSystemWindows="true" を XML に残したまま inset リスナーも書いてしまい、パディングが二重に入って逆に余白が広がった画面があったことです。Android 16 環境では fitsSystemWindows の指定は外し、insets の処理は一箇所に寄せるのが結局いちばん読みやすくなりました。

Jetpack Compose に移行済みの新しいアプリでは、もっと素直でした。Scaffold の innerPadding を内側に渡すか、全画面の壁紙レイヤーには当てず、操作レイヤーにだけ safeDrawingPadding() を当てます。

Box(Modifier.fillMaxSize()) {
    // 壁紙は画面の隅まで描く(ここには insets を当てない)
    WallpaperImage(modifier = Modifier.fillMaxSize())
 
    // 操作 UI はシステムバーを避ける
    PreviewControls(
        modifier = Modifier
            .align(Alignment.TopCenter)
            .safeDrawingPadding(),
    )
}

「画像には当てず、UI にだけ当てる」というこの線引きが、壁紙アプリにおける edge-to-edge 対応の勘どころだと感じています。全部に safeDrawingPadding() を付けると、せっかくの全画面プレビューが従来と変わらなくなってしまいます。

Antigravity をどこで使い、どこで使わなかったか

複数アプリを横断する作業で、Antigravity のエージェントが効いたのは「機械的に拾える対象の洗い出し」でした。opt-out 属性の列挙、statusBarColor の使用箇所の特定、insets リスナーが付いていない Activity の候補出しまでは、エージェントに任せると確実で速かったです。

一方で、最終的なパディングの当たり具合は、必ず実機とエミュレータの両方で自分の目で確認しました。insets は端末によって(特にジェスチャーナビとボタンナビで)値が変わるので、コードが正しく見えても見た目がずれることがあります。宮大工だった祖父たちは、図面が合っていても最後は必ず手で触れて確かめていたそうですが、この種の仕上げの確認だけは人がやるべき部分だと、画面を一枚ずつ見ながら思いました。エージェントに任せる範囲と、自分で確かめる範囲を分けておくことが、横断作業では一番の時短になります。

これから取り組むこと

いまは壁紙アプリ群の対応を一通り終え、3辺ジェスチャーナビ端末での表示確認を実機で詰めている段階です。次は、同じ insets 処理が各アプリにコピペで散らばっているのを、共通モジュールに切り出して一本化したいと考えています。OS の仕様変更が来るたびに全アプリを直す今の構造そのものを、少しずつ軽くしていくのが当面の目標です。

同じように複数アプリを抱えて targetSdk 36 への移行を控えている方は、まず opt-out の使用箇所を横断で洗い出すところから始めると、作業量の見通しが立てやすいと思います。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

▣ アプリ開発2026-10-15
immutable を付けた資産を同じ URL で差し替えたら、利用者のブラウザだけが古いままでした
長期キャッシュを付けた JS を同じ URL で差し替えても、CDN をパージしてもブラウザは取り直しません。同じ URL・no-cache・クエリ付き・内容ハッシュ名の 4 方式を Chromium で並べて比べ、ビルドの最後にハッシュを付ける完全なスクリプトまでをまとめました。
▣ アプリ開発2026-09-29
Cloudflare をパージしても古い壁紙が届き続けた原因は、オリジン側で JPEG を握っていた XPageSpeed
同じ URL に上書きした壁紙画像が、Cloudflare をパージしても古いまま届き続けた運用の記録です。ローカル・オリジン・エッジの三者を突き合わせて層を名指しする検査、プレフィックスパージの使いどころ、Xserver の XPageSpeed を .htaccess で安全に切る書き方まで扱います。
▣ アプリ開発2026-09-19
見積書 PDF の自動化を一度にまとめて頼み、4 つに割り直すまで
受託の見積書を毎回コピーして作っていた手作業を、Antigravity に頼んで小さな PDF 出力ツールへ移した記録です。最初に全部をまとめて頼んで作り直すことになった理由と、データ・計算・見た目・保存の 4 つに割って渡し直した順番を、動くコードと一緒に書き残します。