RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/アプリ開発
アプリ開発/2026-05-28中級

壁紙アプリにスライドショーとページジャンプを実装した手順と判断

綺麗な壁紙 4K/HDR と浮世絵壁紙の Android 版に追加したスライドショーとページジャンプの実装メモです。Handler とライフサイクル、SeekBar 連携、広告との兼ね合いで悩んだ判断を残します。

Android43RecyclerView3ViewPager2個人開発187壁紙アプリ28

ユーザーレビューに何年も繰り返し届いていた要望が 2 つありました。「ベッドで眺めるときに自動でめくれるようにしてほしい」と「壁紙の枚数が多すぎて目当ての一枚に辿り着けない」です。今回 Android 版の「綺麗な壁紙 4K/HDR」と「浮世絵壁紙」をアップデートし、この 2 点に応えるかたちで スライドショーページジャンプスライダー を追加しました。あわせて、これも長く要望をいただいていた 広告非表示オプション も同時にリリースしています。実装そのものは特別なことをしていませんが、2014 年から壁紙アプリを運営してきた個人開発の感覚で、迷った場面と選んだ理由を残しておきます。

既存の RecyclerView を壊さずに自動送りを差し込む

スライドショーは「3 秒ごとに次の壁紙を表示する」だけの機能ですが、自前で Thread を回すと画面回転やバックグラウンド遷移でリークしやすくなります。今回は ViewPager2 が背後に持つ RecyclerView をそのまま動かす方針にして、UI スレッドの Handler を使う形に落としました。

private val mainHandler = Handler(Looper.getMainLooper())
private val advanceRunnable = object : Runnable {
    override fun run() {
        val next = viewPager.currentItem + 1
        if (next < adapter.itemCount) {
            viewPager.setCurrentItem(next, true)  // smoothScroll = true
        } else {
            viewPager.setCurrentItem(0, true)     // 末尾で先頭へループ
        }
        mainHandler.postDelayed(this, INTERVAL_MS)
    }
}
 
fun startSlideshow() {
    mainHandler.removeCallbacks(advanceRunnable)
    mainHandler.postDelayed(advanceRunnable, INTERVAL_MS)
}
 
fun stopSlideshow() {
    mainHandler.removeCallbacks(advanceRunnable)
}

ポイントは「画面タップで停止」を onTouchEvent ではなく ViewPager2.OnPageChangeCallback#onPageScrollStateChanged 経由で拾うことです。ユーザーが手でスワイプしたときも自動送りと判定が衝突しないように、SCROLL_STATE_DRAGGING を検知したら stopSlideshow() を呼ぶ作りにしています。タップ判定だけで止めようとすると、慣性スクロール中に次のページへ進んでしまう不具合が初期実装で出ました。

ページジャンプは SeekBar とプレビューの 2 層構造

10 万枚規模の中から特定の一枚に飛ぶ機能を、シークバー 1 本で済ませました。素朴な作りですが、OnSeekBarChangeListener の中で viewPager.setCurrentItem(progress, false) を直接呼ぶと、ドラッグ中に毎フレームレイアウトが走ってカクつきます。

seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(sb: SeekBar?, progress: Int, fromUser: Boolean) {
        if (!fromUser) return
        previewLabel.text = "${progress + 1} / ${adapter.itemCount}"  // 軽量プレビュー
    }
    override fun onStartTrackingTouch(sb: SeekBar?) {
        stopSlideshow()  // ジャンプ中は自動送りを止める
    }
    override fun onStopTrackingTouch(sb: SeekBar?) {
        viewPager.setCurrentItem(sb?.progress ?: 0, false)  // 確定時のみ実位置を動かす
    }
})

ドラッグ中はテキストで「現在地 / 総数」を出すだけに留め、指を離したタイミングで初めて setCurrentItem を呼びます。10 万件規模でもスクラブが滑らかになり、Glide 側のキャンセルと再ロードも最後の 1 回で済むため、メモリ圧と通信量の両方に効きました。

広告非表示オプションとの兼ね合い

「広告を非表示にする選択肢が欲しい」という声も多くいただいていたので、今回のリリースで合わせて入れています。動作の実体は、永久課金 / サブスクリプションによる ad-free と、リワード広告視聴による期間限定 ad-free の状態を OR で合成した値を Globals.SHOW_AD として参照する仕組みです。スライドショー中はインタースティシャル広告が割り込むと体験が壊れるため、自動送りが走っている間は背面のインタースティシャル呼び出しを抑制する条件分岐を入れました。

過去にバックボタン経由の広告ゲートを入れ子で書いていた頃、優先度が暗黙化してデバッグが本当に辛くなった記憶があります。その反省を継いで、今回はスライドショーフラグ・ad-free フラグ・広告カウンタを独立した変数として並列に扱うようにしました。状態を並べておくと、後から「誰が誰を抑制したか」をログだけで追えるようになります。

ロック・回転・バックグラウンドへの対応

onPause で必ず stopSlideshow() を呼ぶ、onResume では自動で再開しないという地味な対応が重要でした。スライドショーが裏で回ったまま画面ロックされると、解除した瞬間に複数枚スキップして見えてしまうためです。

回転時は ViewModel に「現在ページ」と「スライドショー実行中フラグ」を保存し、onSaveInstanceState でも併用しています。両方持つのは冗長ですが、累計 5,000 万 DL のレンジで動かすと「Activity 全破棄を伴うクラッシュ復帰」と「単純な構成変更」のどちらも一定頻度で出るので、保険として残しています。

次に検討していること

スライドショーの間隔を設定画面から変えられるようにすること、ロック画面に直接設定するショートカットを足すこと、そして同じ仕組みを iOS 版に逆移植することを次の課題に置いています。祖父が宮大工で「手を動かすことが一つの信心」と教わって育った家庭の感覚が私にも残っていて、初回リリース時の設計に対して納得していない部分が今もあります。今回のアップデートはその一部を少しずつ整理していくきっかけになりました。

もしご利用のなかでお気づきの点があれば、Google Play のレビューから教えていただけると嬉しいです。実機でしか拾えない挙動が必ずあるので、引き続き 1 件ずつ手で返信しながら反映していきます。お読みいただきありがとうございました。

シェア

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

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

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

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

関連記事

アプリ開発2026-05-29
Android 壁紙アプリ v2.1.0 公開後の保守 — Crashlytics と Claude in Chrome で回す日々の運用
Beautiful 4K/HDR Wallpapers v2.1.0 と Ukiyo-e Wallpapers v1.8.0 の段階公開後、Crashlytics 監視と新規クラッシュ対応を Claude in Chrome に任せる運用に切り替えました。RecyclerView の防御的コピーや Glide の desugar など、実機で踏んだ落とし穴を保守目線でまとめます。
アプリ開発2026-05-29
壁紙アプリ6本のリリースノートを同期して書いた3週間の所感
Rork で並行運用している壁紙アプリ6本の What's New を同じ週に揃えて書く、という地味な運用を3週間続けて見えてきた所感をまとめます。
AI モデル2026-05-20
Rork と Claude on Xcode を並行で回した1ヶ月の所感 — 壁紙アプリ更新で見えた使い分け
iOS と Android の壁紙アプリを2014年から個人で運用してきた立場から、Rork と Claude on Xcode を同時に机に並べた1ヶ月の実際の使い分けを記録します。新規と既存で求められる作業の質が違う、という当たり前の話を運用の温度で残しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →