ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。Androidアプリ開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。ご了承ください。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2021-05-08

build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.4.32"
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app/build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "jp.aosystem.needleinhaystack"
        minSdkVersion 21
        targetSdkVersion 30
        multiDexEnabled true
        versionCode 7
        versionName "1.6"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'com.google.android.gms:play-services-ads:20.1.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.aosystem.needleinhaystack">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NeedleInHaystack">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SettingActivity" />
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-0000000000000000~0000000000">
    </application>

</manifest>

app/src/main/java/jp/aosystem/needleinhaystack/MainActivity.kt

package jp.aosystem.needleinhaystack

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.Chronometer
import android.widget.Chronometer.OnChronometerTickListener
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.setPadding
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.MobileAds
import jp.aosystem.needleinhaystack.databinding.ActivityMainBinding
import java.util.*
import kotlin.random.Random


class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: ConstraintLayout
    //
    private val resourceHays: Array<Triple<Int, Int, Int>> = arrayOf(
            //resource,width,height
            Triple(R.drawable.ic_hay1, 164, 23),
            Triple(R.drawable.ic_hay2, 182, 13),
            Triple(R.drawable.ic_hay3, 231, 26),
            Triple(R.drawable.ic_hay4, 298, 21),
            Triple(R.drawable.ic_hay5, 300, 28),
            Triple(R.drawable.ic_hay6, 323, 33),
            Triple(R.drawable.ic_hay7, 356, 22),
            Triple(R.drawable.ic_hay8, 355, 22),
            Triple(R.drawable.ic_hay9, 395, 29),
            Triple(R.drawable.ic_hay10, 404, 19),
            Triple(R.drawable.ic_hay11, 468, 28),
            Triple(R.drawable.ic_hay12, 466, 20),
            Triple(R.drawable.ic_hay13, 497, 31),
            Triple(R.drawable.ic_hay14, 507, 26),
            Triple(R.drawable.ic_hay15, 537, 24),
    )
    private val levels: List<Int> = listOf(
            5,      //level0 training mode
            10,    //level1
            100,    //level2
            200,    //level3
            300,    //level4
            400,    //level5
            500,    //level6
            600,    //level7
            700,    //level8
            800,    //level9
            900,   //level10
    )
    private val haySizeRatios: List<Float> = listOf(  //hayの拡大率
            2F,      //level0 training mode
            1.6F,    //level1
            1.4F,    //level2
            1.2F,    //level3
            1.1F,    //level4
            1.1F,    //level5
            1.1F,    //level6
            1.0F,    //level7
            0.9F,    //level8
            0.8F,    //level9
            0.7F,   //level10
    )
    private val resourceNeedle: Triple<Int, Int, Int> = Triple(R.drawable.ic_needle, 133, 5)    //needle
    private val resourceNeedleTraining: Triple<Int, Int, Int> = Triple(R.drawable.ic_needle_red, 133, 5)    //needle red
    private val needleSizeRatio: Float = 2F
    private var baseWidth: Int = 0      //描画エリア
    private var baseHeight: Int = 0     //描画エリア
    private var destroyFlag: Boolean = false    //Activity破棄された場合など
    private var difficultyLevel: Int = 1       //難易度
    private var elapsedTimeDisplay: Int = 0     //経過時間表示
    private var themeNumber: Int = 0    //テーマの種類
    private var localeLanguage: String = ""
    private lateinit var stageLayout: LinearLayout  //針をここに置く
    private lateinit var stageText: TextView        //針をここに置く位置のテキスト
    private lateinit var chronometer: Chronometer   //経過時間
    private var trainingColorFlag: Boolean = false      //false:銀色 true:赤色 交互に点滅
    private var hayLayouts: Array<LinearLayout> = arrayOf() //hayの実体
    private lateinit var needleLayout: LinearLayout //needleの実体
    private lateinit var needleImg: ImageView   //needle画像
    private var touchX: Float = 0F      //タッチした位置
    private var touchY: Float = 0F      //タッチした位置
    private var touchViewX: Float = 0F  //タッチされたViewの初期位置
    private var touchViewY: Float = 0F  //タッチされたViewの初期位置
    private var catchHay: Int = -1       //掴んだhayの番号
    private var catchNeedle: Boolean = false    //needleを掴んだらtrue

    //adMob
    private lateinit var adView: AdView     //adMob
    private val adSize: AdSize
        get() {
            val display = windowManager.defaultDisplay
            val outMetrics = DisplayMetrics()
            display.getMetrics(outMetrics)
            val density = outMetrics.density
            var adWidthPixels = this.binding.adContainer.width.toFloat()
            if (adWidthPixels == 0f) {
                adWidthPixels = outMetrics.widthPixels.toFloat()
            }
            val adWidth = (adWidthPixels / density).toInt()
            return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(this, adWidth)
        }

    companion object {
        //private const val AD_UNIT_ID: String = "ca-app-pub-3940256099942544/6300978111"     //adMob Test
        private const val AD_UNIT_ID: String = "ca-app-pub-0000000000000000/0000000000"     //adMob
        private const val RESULT_SETTING_ACTIVITY: Int = 1
        internal const val SETTINGS: String = "settings"
        internal const val THEME_NUMBER: String = "themeNumber"
        internal const val LOCALE_LANGUAGE: String = "localeLanguage"
        internal const val DIFFICULTY_LEVEL: String = "difficultyLevel"
        internal const val ELAPSED_TIME_DISPLAY: String = "elapsedTimeDisplay"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        this.binding = ActivityMainBinding.inflate(layoutInflater)
        this.viewModel = this.binding.root
        setContentView(this.viewModel)
        //タイトルバー非表示
        supportActionBar?.hide()
        //設定読み出し
        this.loadDifficultyLevel()
        this.loadElapsedTimeDisplay()
        this.loadThemeNumber()
        this.setTheme()
        //adMob
        MobileAds.initialize(this) {}
        this.adView = AdView(this)
        this.binding.adContainer.addView(this.adView)
        this.loadBanner()
    }

    override fun onResume() {
        super.onResume()
        //準備が出来たらwidth,heightを取得して次へ進む
        this.binding.layoutBase.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                this@MainActivity.binding.layoutBase.viewTreeObserver.removeOnGlobalLayoutListener(this)
                this@MainActivity.baseWidth = this@MainActivity.binding.layoutBase.width
                this@MainActivity.baseHeight = this@MainActivity.binding.layoutBase.height
                this@MainActivity.initHaystack()
            }
        })
    }

    override fun onDestroy() {
        this.destroyFlag = true
        super.onDestroy()
    }

    //adMob
    private fun loadBanner() {
        this.adView.adUnitId = AD_UNIT_ID
        this.adView.adSize = adSize
        val adRequest = AdRequest
            .Builder()
            //.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
            .build()
        this.adView.loadAd(adRequest)
    }

    private fun initHaystack() {
        //針の移動場所マージン
        val stageMarginLayoutPadding: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18F, resources.displayMetrics).toInt()
        val stageMarginLayout: LinearLayout = LinearLayout(this)
        stageMarginLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        stageMarginLayout.setPadding(stageMarginLayoutPadding)
        stageMarginLayout.setBackgroundColor(Color.rgb(105, 41, 38))
        //針の移動場所
        val stageLayoutMinHeight: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36F, resources.displayMetrics).toInt()
        this.stageLayout = LinearLayout(this)
        this.stageLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        this.stageLayout.minimumHeight = stageLayoutMinHeight
        this.stageLayout.gravity = Gravity.CENTER
        this.stageLayout.setBackgroundColor(Color.rgb(140, 52, 48))
        //針の移動場所テキスト
        val stageTextSize: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 5F, resources.displayMetrics)
        this.stageText = TextView(this)
        this.stageText.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        this.stageText.setTextColor(Color.rgb(77, 3, 9))
        this.stageText.textSize = stageTextSize
        this.stageText.text = resources.getString(R.string.moveTheNeedleHere) + "  LEVEL." + this.difficultyLevel.toString() + "  "
        this.chronometer = Chronometer(this)
        if (elapsedTimeDisplay == 0) {
            this.chronometer.setTextColor(Color.rgb(140,52,48))  //見えない色
        } else {
            this.chronometer.setTextColor(Color.rgb(77, 3, 9))  //見える色
        }
        this.chronometer.textSize = stageTextSize
        //針画像
        this.needleImg = ImageView(this)
        //トレーニングモードの時のneedle色変化
        if (this.difficultyLevel == 0) {
            this.chronometer.onChronometerTickListener = OnChronometerTickListener {
                if (trainingColorFlag) {
                    this.needleImg.setImageResource(this.resourceNeedle.first)
                    this.trainingColorFlag = false
                } else {
                    this.needleImg.setImageResource(this.resourceNeedleTraining.first)
                    this.trainingColorFlag = true
                }
            }
        }
        //
        this.stageLayout.addView(this.stageText)
        this.stageLayout.addView(this.chronometer)
        stageMarginLayout.addView(this.stageLayout)
        this.binding.layoutBase.addView(stageMarginLayout)
        //
        this.start()
        this.chronometer.start()
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun start() {
        val touchListener: OnTouchListener = OnTouchListener { v2: View, event ->
            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    this.touchX = event.rawX
                    this.touchY = event.rawY
                    this.actionDown(v2)
                    true
                }
                MotionEvent.ACTION_MOVE -> {
                    val moveX = event.rawX - this.touchX
                    val moveY = event.rawY - this.touchY
                    this.actionMove(v2, moveX, moveY)
                    true
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    this.actionUp(v2)
                    true
                }
                else -> false
            }
        }
        val baseCenterX: Int = this.baseWidth.div(2)
        val baseCenterY: Int = this.baseHeight.div(2)
        val pixel: Float = this.baseWidth.div(3F).div(600F)
        val needlePos: Int = this.levels[this.difficultyLevel].div(3)   //needleは干し草の1/3の位置に埋め込む
        this.hayLayouts = arrayOf()
        var resourceHayPointer: Int = 0
        for (i in 0..this.levels[this.difficultyLevel]) {
            val (resourceHay, hayW, hayH) = resourceHays[resourceHayPointer]
            val img: ImageView = ImageView(this)
            img.setImageResource(resourceHay)
            val lay: LinearLayout = LinearLayout(this)
            lay.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
            val hayWidth: Int = hayW.toFloat().times(pixel).times(this.haySizeRatios[this.difficultyLevel]).toInt()
            val hayHeight: Int = hayH.toFloat().times(pixel).times(this.haySizeRatios[this.difficultyLevel]).toInt()
            lay.addView(img, hayWidth, hayHeight)
            lay.x = (baseCenterX - hayWidth.div(2)) + Random.nextInt(this.baseWidth.div(3)).toFloat() - Random.nextInt(this.baseWidth.div(3)).toFloat()
            lay.y = (baseCenterY - hayHeight.div(2)) + Random.nextInt(this.baseHeight.div(3)).toFloat() - Random.nextInt(this.baseHeight.div(3)).toFloat()
            lay.setPadding(0, 5, 0, 5)
            lay.rotation = Random.nextInt(360).toFloat()
            lay.setOnTouchListener(touchListener)
            this.binding.layoutBase.addView(lay)
            this.hayLayouts += lay
            //
            if (i == needlePos) {
                //needle
                val (rsoNeedle, needleW, needleH) = this.resourceNeedle
                this.needleImg.setImageResource(rsoNeedle)
                this.needleLayout = LinearLayout(this)
                this.needleLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
                val needleWidth: Int = needleW.toFloat().times(pixel).times(this.needleSizeRatio).toInt()
                val needleHeight: Int = needleH.toFloat().times(pixel).times(this.needleSizeRatio).toInt()
                this.needleLayout.addView(this.needleImg, needleWidth, needleHeight)
                this.needleLayout.x = (baseCenterX - needleWidth.div(2)) + Random.nextInt(this.baseWidth.div(4)).toFloat() - Random.nextInt(this.baseWidth.div(4)).toFloat()
                this.needleLayout.y = (baseCenterY - needleHeight.div(2)) + Random.nextInt(this.baseHeight.div(4)).toFloat() - Random.nextInt(this.baseHeight.div(4)).toFloat()
                this.needleLayout.setPadding(0, 5, 0, 5)
                this.needleLayout.rotation = Random.nextInt(360).toFloat()
                this.needleLayout.setOnTouchListener(touchListener)
                this.binding.layoutBase.addView(this.needleLayout)
            }
            //
            resourceHayPointer += 1
            if (resourceHayPointer >= resourceHays.size) {
                resourceHayPointer = 0
            }
        }
    }

    fun onClickStart(v: View) {
        this.binding.layoutBase.removeAllViews()
        this.initHaystack()
    }

    private fun actionDown(v: View) {
        for (i in this.hayLayouts.indices) {
            if (v == this.hayLayouts[i]) {
                this.catchHay = i   //タッチされたViewを覚える
                this.touchViewX = this.hayLayouts[i].x   //初期位置を保存
                this.touchViewY = this.hayLayouts[i].y   //初期位置を保存
                this.catchNeedle = false
                return
            }
        }
        if (v == this.needleLayout) {
            this.catchNeedle = true     //needleをキャッチ
            this.touchViewX = this.needleLayout.x   //初期位置を保存
            this.touchViewY = this.needleLayout.y   //初期位置を保存
            this.catchHay = -1
            return
        }
        this.catchHay = -1
        this.catchNeedle = false
    }

    private fun actionMove(v: View, moveX: Float, moveY: Float) {
        if (this.catchHay != -1 && v == this.hayLayouts[this.catchHay]) {
            this.hayLayouts[this.catchHay].x = this.touchViewX + moveX
            this.hayLayouts[this.catchHay].y = this.touchViewY + moveY
            this.catchNeedle = false
            return
        }
        if (this.catchNeedle && v == this.needleLayout) {
            this.needleLayout.x = this.touchViewX + moveX
            this.needleLayout.y = this.touchViewY + moveY
            this.catchHay = -1
            return
        }
        this.catchHay = -1
        this.catchNeedle = false
    }

    private fun actionUp(v: View) {
        if (this.catchNeedle) {
            val stageLayoutX1: Float = this.stageLayout.x   //針の置き場所。左上
            val stageLayoutY1: Float = this.stageLayout.y   //針の置き場所。左上
            val stageLayoutX2: Float = this.stageLayout.x + this.stageLayout.width.toFloat()    //針の置き場所。右下
            val stageLayoutY2: Float = this.stageLayout.y + this.stageLayout.height.toFloat()   //針の置き場所。右下
            val needlePositionX: Float = this.needleLayout.x + this.needleLayout.width.div(2)   //needleの位置
            val needlePositionY: Float = this.needleLayout.y + this.needleLayout.height.div(2)  //needleの位置
            if (needlePositionX in stageLayoutX1..stageLayoutX2 && needlePositionY in stageLayoutY1..stageLayoutY2) {
                this.stageLayout.setBackgroundColor(Color.RED)
                this.stageText.text = resources.getString(R.string.congratulations) + "  LEVEL." + this.difficultyLevel.toString() + "  "
                this.stageText.setTextColor(Color.WHITE)
                this.chronometer.stop()
                this.chronometer.setTextColor(Color.WHITE)
                this.needleImg.setImageResource(this.resourceNeedle.first)
            }
        }
        this.catchHay = -1
        this.catchNeedle = false
    }

    fun onClickSetting(v: View) {
        this.destroyFlag = true
        this.binding.layoutBase.removeAllViews()
        this.chronometer.stop()
        val intent = Intent(applicationContext, SettingActivity::class.java)
        intent.putExtra(DIFFICULTY_LEVEL, this.difficultyLevel)
        intent.putExtra(ELAPSED_TIME_DISPLAY, this.elapsedTimeDisplay)
        intent.putExtra(THEME_NUMBER, this.themeNumber)
        intent.putExtra(LOCALE_LANGUAGE, this.localeLanguage)
        startActivityForResult(intent, RESULT_SETTING_ACTIVITY)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        if (resultCode == Activity.RESULT_OK && requestCode == RESULT_SETTING_ACTIVITY && intent != null) {
            val lastDifficultyLevel = this.difficultyLevel
            this.difficultyLevel = intent.getIntExtra(DIFFICULTY_LEVEL, 1)
            if (lastDifficultyLevel != this.difficultyLevel) {
                this.saveDifficultyLevel()
            }
            val lastElapsedTimeDisplay = this.elapsedTimeDisplay
            this.elapsedTimeDisplay = intent.getIntExtra(ELAPSED_TIME_DISPLAY, 0)
            if (lastElapsedTimeDisplay != this.elapsedTimeDisplay) {
                this.saveElapsedTimeDisplay()
            }
            val lastThemeNumber = this.themeNumber
            this.themeNumber = intent.getIntExtra(THEME_NUMBER, 0)
            if (lastThemeNumber != this.themeNumber) {
                this.saveThemeNumber()
            }
            val lastLocaleLanguage = this.localeLanguage
            this.localeLanguage = intent.getStringExtra(LOCALE_LANGUAGE) ?: ""
            if (this.localeLanguage != lastLocaleLanguage) {
                this.saveLocaleLanguage()
            }
        }
        recreate()
    }

    //-------------------------------------------------

    //difficultyLevelを保存
    private fun saveDifficultyLevel() {
        getSharedPreferences(SETTINGS, Context.MODE_PRIVATE).edit().apply {
            putInt(DIFFICULTY_LEVEL, this@MainActivity.difficultyLevel)
            apply()
        }
    }

    //difficultyLevelを読み出し
    private fun loadDifficultyLevel() {
        val pref = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE)
        this.difficultyLevel = pref.getInt(DIFFICULTY_LEVEL, 1)
    }

    //elapsedTimeDisplayを保存
    private fun saveElapsedTimeDisplay() {
        getSharedPreferences(SETTINGS, Context.MODE_PRIVATE).edit().apply {
            putInt(ELAPSED_TIME_DISPLAY, this@MainActivity.elapsedTimeDisplay)
            apply()
        }
    }

    //elapsedTimeDisplayを読み出し
    private fun loadElapsedTimeDisplay() {
        val pref = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE)
        this.elapsedTimeDisplay = pref.getInt(ELAPSED_TIME_DISPLAY, 0)
    }

    //テーマを保存
    private fun saveThemeNumber() {
        getSharedPreferences(SETTINGS, Context.MODE_PRIVATE).edit().apply {
            putInt(THEME_NUMBER, this@MainActivity.themeNumber)
            apply()
        }
    }

    //テーマを読み出し
    private fun loadThemeNumber() {
        val pref = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE)
        this.themeNumber = pref.getInt(THEME_NUMBER, 0)
    }

    //テーマを設定
    private fun setTheme() {
        when (this.themeNumber) {
            0 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            1 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
        }
    }

    //-------------------------------------------------
    //localeLanguageを保存
    private fun saveLocaleLanguage() {
        getSharedPreferences(SETTINGS, Context.MODE_PRIVATE).edit().apply {
            putString(LOCALE_LANGUAGE, this@MainActivity.localeLanguage)
            apply()
        }
    }

    //言語設定
    override fun attachBaseContext(base: Context) {
        val pref = base.getSharedPreferences(SETTINGS, Context.MODE_PRIVATE)
        this.localeLanguage = pref.getString(LOCALE_LANGUAGE, "") ?: ""
        val loc: Locale? = if (this.localeLanguage != "") Locale(this.localeLanguage) else null
        if (loc != null) {
            val res = base.resources
            val config = Configuration(res.configuration)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {    //minSdkVersion 24
                val localeList = LocaleList(loc)
                LocaleList.setDefault(localeList)
                config.setLocales(localeList)
            } else {    //minSdkVersion 17  16はダメ
                config.setLocale(loc)
            }
            super.attachBaseContext(base.createConfigurationContext(config))
        } else {
            super.attachBaseContext(base)
        }
    }

    //----------------------------------------------


}

app/src/main/java/jp/aosystem/needleinhaystack/SettingActivity.kt

package jp.aosystem.needleinhaystack

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import jp.aosystem.needleinhaystack.databinding.ActivitySettingBinding
import java.util.*

class SettingActivity : AppCompatActivity() {
    private lateinit var binding: ActivitySettingBinding
    private lateinit var viewModel: ConstraintLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        this.binding = ActivitySettingBinding.inflate(layoutInflater)
        this.viewModel = this.binding.root
        setContentView(this.viewModel)
        //データ受け取り
        val intent = this.intent
        val difficultyLevel: Int = intent.getIntExtra(MainActivity.DIFFICULTY_LEVEL,1)
        val elapsedTimeDisplay: Int = intent.getIntExtra(MainActivity.ELAPSED_TIME_DISPLAY,0)
        when (difficultyLevel) {
            0 -> this.binding.radioLevel0.isChecked = true
            1 -> this.binding.radioLevel1.isChecked = true
            2 -> this.binding.radioLevel2.isChecked = true
            3 -> this.binding.radioLevel3.isChecked = true
            4 -> this.binding.radioLevel4.isChecked = true
            5 -> this.binding.radioLevel5.isChecked = true
            6 -> this.binding.radioLevel6.isChecked = true
            7 -> this.binding.radioLevel7.isChecked = true
            8 -> this.binding.radioLevel8.isChecked = true
            9 -> this.binding.radioLevel9.isChecked = true
            10 -> this.binding.radioLevel10.isChecked = true
        }
        this.binding.switchTime.isChecked = elapsedTimeDisplay != 0
        val themeNumber: Int = intent.getIntExtra(MainActivity.THEME_NUMBER,0)
        this.binding.switchTheme.isChecked = themeNumber != 0
        val localeLanguage: String = intent.getStringExtra(MainActivity.LOCALE_LANGUAGE) ?: ""
        when (localeLanguage) {
            "en" -> this.binding.radioLanguageEn.isChecked = true
            "ja" -> this.binding.radioLanguageJa.isChecked = true
            else -> this.binding.radioLanguageSystem.isChecked = true
        }
        //
        supportActionBar?.hide()    //タイトルバー非表示
    }

    fun onClickApply(v: View) {
        var difficultyLevel: Int = 1
        if (this.binding.radioLevel0.isChecked) {
            difficultyLevel = 0
        } else if (this.binding.radioLevel1.isChecked) {
            difficultyLevel = 1
        } else if (this.binding.radioLevel2.isChecked) {
            difficultyLevel = 2
        } else if (this.binding.radioLevel3.isChecked) {
            difficultyLevel = 3
        } else if (this.binding.radioLevel4.isChecked) {
            difficultyLevel = 4
        } else if (this.binding.radioLevel5.isChecked) {
            difficultyLevel = 5
        } else if (this.binding.radioLevel6.isChecked) {
            difficultyLevel = 6
        } else if (this.binding.radioLevel7.isChecked) {
            difficultyLevel = 7
        } else if (this.binding.radioLevel8.isChecked) {
            difficultyLevel = 8
        } else if (this.binding.radioLevel9.isChecked) {
            difficultyLevel = 9
        } else if (this.binding.radioLevel10.isChecked) {
            difficultyLevel = 10
        }
        val elapsedTimeDisplay = if (this.binding.switchTime.isChecked) 1 else 0
        val themeNumber = if (this.binding.switchTheme.isChecked) 1 else 0
        var localeLanguage: String = ""
        if (this.binding.radioLanguageEn.isChecked) {
            localeLanguage = "en"
        } else if (this.binding.radioLanguageJa.isChecked) {
            localeLanguage = "ja"
        }
        intent.putExtra(MainActivity.DIFFICULTY_LEVEL, difficultyLevel)
        intent.putExtra(MainActivity.ELAPSED_TIME_DISPLAY, elapsedTimeDisplay)
        intent.putExtra(MainActivity.THEME_NUMBER, themeNumber)
        intent.putExtra(MainActivity.LOCALE_LANGUAGE, localeLanguage)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    fun onClickCancel(v: View) {
        val intent = Intent()
        setResult(Activity.RESULT_CANCELED, intent)
        finish()
    }

    //--------------------------------------------------------------------------------

    //言語設定
    override fun attachBaseContext(base: Context) {
        val pref = base.getSharedPreferences(MainActivity.SETTINGS, Context.MODE_PRIVATE)
        val localeLanguage: String = pref.getString(MainActivity.LOCALE_LANGUAGE, "") ?: ""
        val loc: Locale? = if (localeLanguage != "") Locale(localeLanguage) else null
        if (loc != null) {
            val res = base.resources
            val config = Configuration(res.configuration)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {    //minSdkVersion 24
                val localeList = LocaleList(loc)
                LocaleList.setDefault(localeList)
                config.setLocales(localeList)
            } else {    //minSdkVersion 17  16はダメ
                config.setLocale(loc)
            }
            super.attachBaseContext(base.createConfigurationContext(config))
        } else {
            super.attachBaseContext(base)
        }
    }

    //--------------------------------------------------------------------------------

}

app/src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <LinearLayout
        android:id="@+id/layoutBackground"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@drawable/haystack"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:id="@+id/layoutHeader"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/textTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:background="#7CB342"
                android:gravity="center"
                android:minHeight="48dip"
                android:text="@string/needleHaystack" />

            <TextView
                android:id="@+id/textRestart"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="#C0CA33"
                android:gravity="center"
                android:minHeight="48dip"
                android:onClick="onClickStart"
                android:text="@string/start" />

            <TextView
                android:id="@+id/textSetting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="#FDD835"
                android:gravity="center"
                android:minHeight="48dip"
                android:onClick="onClickSetting"
                android:text="@string/setting" />

        </LinearLayout>

        <FrameLayout
            android:id="@+id/layoutBase"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="vertical" />

        <Space
            android:layout_width="match_parent"
            android:layout_height="80dp" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/ad_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent" />



</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/layout/activity_setting.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SettingActivity">
    <LinearLayout
        android:id="@+id/layoutButtons"
        android:layout_width="match_parent"
        android:layout_height="48dip"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/textCancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="6"
            android:background="#FDD835"
            android:gravity="center"
            android:minHeight="48dip"
            android:onClick="onClickCancel"
            android:text="@string/cancel" />

        <TextView
            android:id="@+id/textVerification"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="6"
            android:background="#C0CA33"
            android:gravity="center"
            android:minHeight="48dip"
            android:onClick="onClickApply"
            android:text="@string/apply" />

    </LinearLayout>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/layoutButtons">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:orientation="vertical"
            android:paddingLeft="20dp"
            android:paddingTop="20dp"
            android:paddingRight="20dp"
            android:paddingBottom="50dp">


            <TextView
                android:id="@+id/textLevel"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/level"
                android:textColor="?android:attr/textColorPrimary" />

            <RadioGroup
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="10dp">

                <RadioButton
                    android:id="@+id/radioLevel0"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level0" />

                <RadioButton
                    android:id="@+id/radioLevel1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level1" />

                <RadioButton
                    android:id="@+id/radioLevel2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level2" />

                <RadioButton
                    android:id="@+id/radioLevel3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level3" />

                <RadioButton
                    android:id="@+id/radioLevel4"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level4" />

                <RadioButton
                    android:id="@+id/radioLevel5"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level5" />

                <RadioButton
                    android:id="@+id/radioLevel6"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level6" />

                <RadioButton
                    android:id="@+id/radioLevel7"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level7" />

                <RadioButton
                    android:id="@+id/radioLevel8"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level8" />

                <RadioButton
                    android:id="@+id/radioLevel9"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level9" />

                <RadioButton
                    android:id="@+id/radioLevel10"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="18dp"
                    android:text="@string/level10" />
            </RadioGroup>

            <View
                android:id="@+id/divider1"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="20dp"
                android:background="?android:attr/listDivider" />

            <androidx.appcompat.widget.SwitchCompat
                android:id="@+id/switchTime"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="15dp"
                android:text="@string/elapsedTimeDisplay" />

            <View
                android:id="@+id/divider2"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="20dp"
                android:background="?android:attr/listDivider" />

            <androidx.appcompat.widget.SwitchCompat
                android:id="@+id/switchTheme"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="15dp"
                android:text="@string/darkTheme" />

            <View
                android:id="@+id/divider4"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="10dp"
                android:background="?android:attr/listDivider" />

            <TextView
                android:id="@+id/textLanguage"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="@string/language"
                android:textColor="?android:attr/textColorPrimary" />

            <RadioGroup
                android:layout_width="match_parent"
                android:layout_height="match_parent" >
                <RadioButton
                    android:id="@+id/radioLanguageSystem"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/languageSystem" />
                <RadioButton
                    android:id="@+id/radioLanguageEn"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/languageEn" />
                <RadioButton
                    android:id="@+id/radioLanguageJa"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/languageJa" />

            </RadioGroup>

            <View
                android:id="@+id/divider3"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="20dp"
                android:background="?android:attr/listDivider" />

            <TextView
                android:id="@+id/textUsage"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="@string/usage" />

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:adjustViewBounds="true"
                android:contentDescription="@string/usage"
                app:srcCompat="@drawable/usage1" />

            <Space
                android:layout_width="match_parent"
                android:layout_height="100dp" />

        </LinearLayout>
    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>