タブレット用プログラムの書き止め

android OS & iPadOS の記録。

SFC互換機 Book-offブランド 16BIT COMPACT

2024-03-21 16:23:18 | 気まぐれ言の葉

鳥山さんの訃報でドラクエが気になり押し入れからSFCを発掘して起動。

まともに動かない(-_-;)

検索の説明のように手当てをしたが変わらず、

深くネットをさらうとVRAMの劣化で正常に動作しなくなる事もあるらしい。

LSIメモリ自体無いだろうからこの場合は修理はできないはず。

完全動作は保証してないものの互換機がいくつか出ていたが少々高い。

2024発売のBook-offブランド 16BIT COMPACT 税込7,000円弱。

他の互換機と比べると格安なので買ってみた。

 

AVケーブルが細い。1/3位の太さ。性能が気になるけど自前のと交換すればいいかな。

13インチの液晶モニタでHDMI入力。RCA入力HDMI出力のコンバータを使って接続。

コンバータに4:3補正があるので当時の縦横比で表示。

単純のドット合わせの拡大なので滲み等が出ます。液晶だし。

昔のブラウン管モニターなら本家SFCと同じ画質なのでしょう。

640x480ドットの小さい液晶モニターなら滲みがないかもしれない。

S端子接続でもハードでアップスケールしてくれるTV、レコーダーなら綺麗かも。

設定でアスペクト比を4:3にできれば、昔の感じでプレイできるでしょう。

 

SFCのカートリッジ。

ドラクエ I・II
ドラクエ Ⅳ
ドラクエ Ⅴ

その他RPG数本

 

何日も夜更かしして、のめり込んだ事を思い出します。
ラスボス倒すのに苦労してクリア。
脱力感が凄かったけど、また初めからやり直してたのは何だったんだろう。

 

 

天国でも作品を作っていてくれたら嬉しいです。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【kotlin】 Floating Menu 自作

2024-03-18 15:56:55 | Android studio 日記

ディレクトリの移動の前にコンテキストメニューのようなものを作る。

画像をロングプレスするとお気に入りリストにパスを登録してたんだけど、
その場所にメニューを開いて機能選択できたら色々都合が良いかなぁ。

別のフラグメントを用意するのではなく、今のフラグメントにViewをXMLに追加してON、OFFする。
台紙部分をGONEすれば、無いものとなるので楽だ。
非表示で表示場所(X,Y)を設定して表示させる。移動はさせないものとする。

問題もある。
Viewをはじめに描画構築した時は表示直後にそのViewのプロパティを参照しても正常値が戻ってこない。更新タイムラグ。

メニューの表示場所をスクリーン内に修正表示させる場合はフラグメント切り替え時のView表示初回のみ位置修正ができない。
Viewの幅と高さが0のままで本当の数値が取得できないのでメニューが右外、下外に出てしまったらスクリーン内に戻せない。

ただ、XMLでメニューの大体の大きさは推し量ることができる。
それをDimens.xml でMenuWidth、MenuHeightと登録しておく。


【fragment_main.xml】

    < LinearLayout
        android:id="@+id/menu"
        android:padding="20dp"
        省略
        >

        < TextView
            android:layout_width="@dimen/menu_width"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            省略
            />

        < TextView
            android:layout_width="@dimen/menu_width"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            省略
            />

   

android:padding と android:layout_margin でメニュー数が変わっても大体の高さを推測できる。


【Dimens.xml】
    < dimen name="menu_width">200dp</dimen>
    < dimen name="menu_height">130dp</dimen>
    < dimen name="menu_offset">40dp</dimen>


【MainFragment.kt】

class MainFragment : Fragment() {
    // 省略

    private fun initFloatingMenu() {
        val menu: LinearLayout = requireActivity().findViewById(R.id.menu)
        menu.visibility = View.GONE

        val menuTextView1: TextView = requireActivity().findViewById(R.id.menu1)
        menuTextView1.setOnClickListener(View.OnClickListener {
            //
        })

        val menuTextViewDone: TextView = requireActivity().findViewById(R.id.menuDone)
        menuTextViewDone.setOnClickListener(View.OnClickListener {
            off_FloatingMenu()
        })
    }
    private fun off_FloatingMenu() {
        val menu: LinearLayout = requireActivity().findViewById(R.id.menu)
        menu.visibility = View.GONE

        waitFlag = false
    }


    // ロングプレスのタップ位置が渡されてくる

    private fun on_FloatingMenu(px:Float, py:Float) {
        val menu: LinearLayout = requireActivity().findViewById(R.id.menu)

        val w:Float = requireContext().resources.getDimension(R.dimen.menu_width) +
                requireContext().resources.getDimension(R.dimen.menu_offset) * 2

        var x:Float = px - w * 3/4
        if ( x < 0 )
            x = 20f
        else if ( px >= ( sm!!.getScreenWidth( requireContext() ) - w * 3/4) )
             x = sm!!.getScreenWidth( requireContext() ) - w

        val h:Float = requireContext().resources.getDimension(R.dimen.menu_height)
        var y:Float = py - h + requireContext().resources.getDimension(R.dimen.menu_offset)

        if ( y < 0 )
             y = 20f
        else if ( py >= ( sm!!.getScreenHeight( requireContext() ) -
             requireContext().resources.getDimension(R.dimen.menu_offset) * 2 ) )

            y = sm!!.getScreenHeight( requireContext() ) - h -
             requireContext().resources.getDimension(R.dimen.menu_offset)

        menu.x = x
        menu.y = y

        menu.visibility = View.VISIBLE
        waitFlag = true
    }
}

タップ位置からメニューのオフセット処理をする。-dx、-dy 左上にオフセット。
これは一番下に何もせずに帰るボタンを設置。そのボタンを指の下に表示させるため。

オフセットの結果、メニューがスクリーンの外に出てしまう場合にメニューをスクリーン上に戻し表示させる修正を加える。

2回の処理の後にViewの左上の座標をX,Yに代入。

 

sm!!.getScreenWidth( Context )
Context を渡すと現在のスクリーン幅を返すメソッド。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【kotlin】 サムネイル表示で非同期処理を使いたい

2024-03-16 02:37:56 | Android studio 日記

サムネイル表示のフラグメントをJavaからKotlinへ変換中なんだけど、
Javaの時に思い付きでコードを組んでいたのでもう少しまともの形にしたい。

サムネイル画像を非同期で別々に単独で作成処理をしてキャッシュフォルダに保存する。
フラグメントが終了するとき、未完了の非同期処理を一斉にキャンセルさせる。

コルーチンの説明でキャンセルがあったからその辺を攻めてみる。

気が重い...。

 


Javaの時は
スレッドプールにスレッドを積んでいき、指定の制限数のスレッドを実行してきた。
実行場所もUIかバックかをいつも意識してデータのやり取りも注意が必要だった。

Kotlinのコルーチンは、
スコープでUIかバックかを記載できる。同一メソッドの中でOK?
ビューにアクセスするときはUI部分で記載するのは同じ。コルーチンでは"post()"を使用した。
コルーチンのキャンセルもフラグメントの消滅に合わせてフラグメント内のすべてにキャンセルが実行されるみたい。便利。


RecyclerViewで使うサムネイルを作成してキャッシュファイルに保存する。
システムからサムネイルを呼び出せばいいんだけれど、対象が古いAPIなので自分で作ってキャッシュに入れる。

サムネイルFragment 専用になるからインナークラスでも良いのかな?
とりあえず、やってみて対応しよう。

class MyFragment : Fragment() {
    /* 色々省略 * /

    inner class MyAdapter() : RecyclerView.Adapter() {
        /* 色々省略 * /

        private fun loadImage(position: Int): Bitmap? {
            /* 色々省略 * /

            /* 受け取ったポジションから絶対パスネームを取り出し、キャッシュファイルの絶対パスを調べる。 * /
            if (!cf.exists()) {
                /* 色々省略 * /

                /* キャッシュファイルが無い時はコルーチンへ * /
                runBlocking {
                    asyncTask(imageAbsPaths!!.get(position), recyclerView!!, position)
                }

                /* 一旦リソースの画像をセットしておく。 * /
                return BitmapFactory.decodeResource(
                    requireContext().getResources(),
                    R.drawable.img_file
                )
            }
            return BitmapFactory.decodeFile(cf.absolutePath) /* キャッシュファイルがあれば、Bitmapを返す * /
        }
        /* 色々省略 * /
    }

    private val parentJob = Job()  /* 一度に全部にキャンセルを通知する小細工 * /

    suspend fun asyncTask(path:String, rv: RecyclerView, vID: Int) {  /* 引数はアダプター内の値を受け取っている * /

        val saveSize = requireContext().getResources()
                .getDimension(R.dimen.thumbnail_save_size).toInt()  /* 画像の大きさをResに宣言している * /
        val souFile: File = File(path)
        if (!souFile.exists() || !souFile.isFile) {
            return
        }
        val cacheDir: File? = viewModel.getCacheDir_thumbnail()  /* キャッシュディレクトリを取得 * /
        if (cacheDir == null || !cacheDir.exists() && !cacheDir.mkdirs()) {
            return
        }
        val cacheFile = File(cacheDir, souFile.name)  /* キャッシュファイルのパスを作成 * /
        if (cacheFile.exists()) return  /* キャッシュファイルが存在してたら帰る。無いから来たんだけども * /

        runBlocking {  /* コルーチンへ * /
            val childJob = lifecycleScope.async(parentJob) {  /* 返り値を受け取りたいのでasync() * /
                MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, saveSize)  /* 外部発注、キャッシュ保存 * /
            }

            val result: Int = childJob.await()

            when (result) {
                0 -> if (vID < rv.adapter!!.itemCount) {

                   /* 直接Viewにアクセスすると例外が発生するので更新はpost()を使用 * /
                    rv.post(Runnable { 
                        rv.adapter!!.notifyItemChanged(vID)  /* 保存完了後にポジションを更新させる * /
                    })
                }

                /* 色々省略 * /
            }
        }
    }

    override fun onDestroy() {
        parentJob.cancel()     /* 親をキャンセルすると子供全部にキャンセル通知してくれる * /
        super.onDestroy()
    }
}

lifecycleScopeなのでフラグメントが終了すれば、キャンセルになるのだけれど念のため。
ベースはできていたので、コルーチンの部分だけで数時間ほど検索&トライで完成できました。

自分の構造に合う書式説明が無かったので色々と組み合わせた後に例外を取り除く感じでした^^;
意外とあっさり動いた気がする。
内容もスッキリだし、Javaの AsyncTask()って何だったの準備とか手順が多すぎ。

次はディレクトリ選択フラグメントかな。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Activity -> FragmentA -> FragmentB -> FragmentA で例外発生

2024-03-13 23:49:20 | Android studio 日記

class MainViewModel(app:Application) : AndroidViewModel(app) {

    private var _screenManager: MyScreenManager? = null
    val screenManager: MyScreenManager?
        get() = _screenManager

    init {
        _screenManager = MyScreenManager(getApplication())
    }

}

class FragmentA : Fragment() {

    private val viewModel: MainViewModel by activityViewModels()
    private var sm: MyScreenManager? = null

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

        sm = viewModel.screenManager
    }

    override fun onDestroy() {
        sm.finish()
        sm = null
        super.onDestroy()
    }
}

MainViewModel のプロパティをFragmentAで参照していて、
FragmentBに移る時にFragmentBでは、viewModel.screenManagerは使っていないので
FragmentAの sm で取り扱ってデータをクリアして、FragmentBに移った。

そして、FragmentBからFragmentAに戻ると例外で停止する。なぜ?

MainActivityからFragmentAへ遷移は問題なし。
FragmentBからFragmentAに遷移で例外停止。?


悩みに悩んで試行してもダメ。
プログラムコードを考えるのを止めて、機能でブロック的に考察すると
遷移の違いは、onDestroy() の実行の有無。判明した事は...。

MainViewModel の screenManager の保存しているデータをクリアしていました。
FragmentAが再開するときにそれが無かったので例外停止することに...。


sm.finish() が、viewModel.screenManager内のデータをクリアしてるのを気が付くのに時間かかった...。
sm.finish() を削除したら期待通りに動いた。 疲れたぁ。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【kotlin】 ViewModel で Context を取り扱う

2024-03-11 20:10:06 | Android studio 日記

class.method(Context) 引数でContextを渡して済ませていた。
それができない状況が出て、ViewModel で Context を取得できるように変えた。

ViewModel のプロパティにContextを保存すると、更新で不一致になったり、参照が残るとシステムが廃棄ができなくなりメモリリークを発生させる。
なので、AndroidViewModel() を使い、getContext() が使えるようにする。

 

class MainViewModel(app:Application) : AndroidViewModel(app) {

    fun getContext(): Context {
        return getApplication()
    }
}


利用する場所では、今まで通りで対応はなし。違いは吸収されるらしい。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする