滅するまでの記録

滅するまでの記録

滅するまでの記録です

プログラミング学習日記 2025/11/30 AndroidでPDFスライドショーアプリを作って(もらって)みよう

じゃあMT5行くか!?と思ってたんですけど・・そう言えば1つ忘れてた。

皆さんってちょっと人には見せられない画像の管理ってどうされてますか?なるほど、画像ファイルをフォルダに分けて、PCやタブレットで画像ビューワを開いてスライドショーモードで環境映像っぽく流してる、と、そうおっしゃる。

なるほどね!でも・・ベストの中のベスト、これは絶対に外さない!って画像の場合、PDFにまとめてしまって管理したい・・そう思ったことはないですか?どっちでも良いですか?でも、PDF管理には明確なメリットがありまーす!

それはファイルの移動。ファイルの移動では細かいファイルが多数ある場合、極端にコピー・移動速度が落ちてしまうというデメリットがあります!(経験者ならば分かりますね?)。PDF形式でまとめてしまってれば、これを防ぐことが出来る(ハズ)なのです。

ところが意外なことにPDFファイルをスライドショーのように、任意のインターバルでフリーハンド(両手が使えるに越したことはないからね!)で流す事が出来るソフトが、ホントーッに無いのです。嘘でしょ?このアプリ大氾濫の時代に?

 ということでAntigravityに頼んで作ってもらうことしました(もちろんコードを読んで学習にも使います)。

 条件はこんな感じ。手持ちの時代遅れのタブレットでも使えるように、と。

 

 Antigravityが選んだ拡張機能はコレ。あ、AndroidってJAVAなんだ?触ることは無いと思ってたのにアンドロイドアプリには必要なのか~

 

 即座にコードは生成されたんだけど、実行やらApkファイル作成にはAndroidStudioってのが必要とのことでダウンロードしてます。

 

 ダウンロード中にコードをチラ見。よくわからんが作られたコードはKotlinって言語で書かれているらしい。JAVAを入れた意味は?

 

 あ、そーなのか!じゃあ今後アンドロイドアプリを作ることになる事もあるだろうから覚えておこう。でも、GoogleなんだからGoで統一してくれると良いのだが?(Goが気に入ってるので)

 

 なるほどね・・確かにGoはゲームエンジンも謎の個人制作のEbitenginってのに頼ってるしなぁ

 

 短いコードだしせっかくなので後でじっくりと読みます

 

package com.example.pdfslideshow

import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.ParcelFileDescriptor
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import java.io.IOException

class MainActivity : AppCompatActivity() {

    private lateinit var pdfImageView: ImageView
    private lateinit var btnOpenPdf: Button
    private lateinit var etInterval: EditText
    private lateinit var btnToggleSlideshow: Button

    private var pdfRenderer: PdfRenderer? = null
    private var currentPdfDescriptor: ParcelFileDescriptor? = null
    private var currentPageIndex = 0
    private var totalPages = 0

    private var isSlideshowRunning = false
    private val handler = Handler(Looper.getMainLooper())
    private var slideshowRunnable: Runnable? = null

    private val openPdfLauncher = registerForActivityResult
(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.data?.let { uri ->
                openPdf(uri)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        pdfImageView = findViewById(R.id.pdfImageView)
        btnOpenPdf = findViewById(R.id.btnOpenPdf)
        etInterval = findViewById(R.id.etInterval)
        btnToggleSlideshow = findViewById(R.id.btnToggleSlideshow)

        btnOpenPdf.setOnClickListener {
            stopSlideshow()
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                addCategory(Intent.CATEGORY_OPENABLE)
                type = "application/pdf"
            }
            openPdfLauncher.launch(intent)
        }

        btnToggleSlideshow.setOnClickListener {
            if (isSlideshowRunning) {
                stopSlideshow()
            } else {
                startSlideshow()
            }
        }
    }

    private fun openPdf(uri: Uri) {
        try {
            closePdf() // Close previous if any

            val fileDescriptor = contentResolver.openFileDescriptor(uri, "r")
            if (fileDescriptor != null) {
                currentPdfDescriptor = fileDescriptor
                pdfRenderer = PdfRenderer(fileDescriptor)
                totalPages = pdfRenderer?.pageCount ?: 0
                currentPageIndex = 0

                if (totalPages > 0) {
                    showPage(currentPageIndex)
                    btnToggleSlideshow.isEnabled = true
                    Toast.makeText(this, "PDF Loaded: $totalPages pages",
Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this, "PDF has 0 pages",
Toast.LENGTH_SHORT).show()
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
            Toast.makeText(this, "Error opening PDF", Toast.LENGTH_SHORT).show()
        }
    }

    private fun showPage(index: Int) {
        if (pdfRenderer == null || totalPages == 0) return

        val page = pdfRenderer!!.openPage(index)
       
        // Create a bitmap with the page's dimensions
        val bitmap = Bitmap.createBitmap(page.width, page.height,
Bitmap.Config.ARGB_8888)
       
        // Render the page onto the bitmap
        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
       
        // Display the bitmap
        pdfImageView.setImageBitmap(bitmap)
       
        page.close()
    }

    private fun startSlideshow() {
        val intervalStr = etInterval.text.toString()
        val intervalSeconds = intervalStr.toLongOrNull() ?: 3L
        val intervalMillis = intervalSeconds * 1000

        if (totalPages == 0) return

        isSlideshowRunning = true
        btnToggleSlideshow.text = getString(R.string.stop_slideshow)
        etInterval.isEnabled = false
        btnOpenPdf.isEnabled = false

        slideshowRunnable = object : Runnable {
            override fun run() {
                if (!isSlideshowRunning) return

                currentPageIndex++
                if (currentPageIndex >= totalPages) {
                    currentPageIndex = 0 // Loop back to start
                }
                showPage(currentPageIndex)
                handler.postDelayed(this, intervalMillis)
            }
        }
        handler.postDelayed(slideshowRunnable!!, intervalMillis)
    }

    private fun stopSlideshow() {
        isSlideshowRunning = false
        btnToggleSlideshow.text = getString(R.string.start_slideshow)
        etInterval.isEnabled = true
        btnOpenPdf.isEnabled = true
        slideshowRunnable?.let { handler.removeCallbacks(it) }
    }

    private fun closePdf() {
        try {
            pdfRenderer?.close()
            currentPdfDescriptor?.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        pdfRenderer = null
        currentPdfDescriptor = null
        totalPages = 0
        btnToggleSlideshow.isEnabled = false
    }

    override fun onDestroy() {
        super.onDestroy()
        closePdf()
    }
}

 これを

 

 インストールしたAndroidStudioにフォルダごと読み込ませまして、その後どうすれば良いのかAntigravityに質問

 

 いや、実機を使ったデバッグとか良いから、いきなりインストール可能なApkファイルを出力して欲しいとゴネたいところ。

 

 Antigravityで質問して、エラーが出たらエラーメッセージをAntigravityに貼り付けて修正をお願いして・・とか面倒だなぁ・・と思ってたんですが。なんともタイムリーなことに新しくAndroidStudioにもGeminiを使ったAIサポート機能がついてたという!

 

 (Antigravityに書いてもらった)コードでエラーが出てApkが作れなかったので、Studioのチャット欄を使ってエラー修正を依頼。

 

なんかアイコンの画像を用意してないのに指定してたのが原因だったらしい。なので、Antigravityのコーディング能力に問題はなかったといっても良い。

 

 アイコン画像とか面倒なだけなので適当に作ってくれないか?と依頼したがさすがにそれは無理だった。まあ、アイコンとかいんのでそのままでビルド依頼

 

 Studioの全体映像はこういう感じ。これは・・アンドロイドアプリが作りたい場合はAntigravityを使う必要は全く無いな。もしもこれで一発で動くアンドロイドアプリなんかが出来たりしてしまった日には・・グーグルプレイ(アプリストア)にAIで作られた大量のアプリが溢れかえることになりそう・・・ま、でもそうそう一発で動くなんて事は・・

 

 動いたーっ!UIは説明不要のシンプルさ。数字の部分はインターバルの秒数でOpenPDFボタンで端末内のPDFファイルを選択、Start SlideShowボタンでスライドショー開始・・・

 凄すぎやろAI!

 それでも・・それでも僕は手打ちのコーディングしかプログラミングとは認めたくないけどね!