ソースコード 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.stickfromthebush"
        minSdkVersion 21
        targetSdkVersion 30
        multiDexEnabled true
        versionCode 6
        versionName "1.5"
        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.stickfromthebush">

    <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.StickFromTheBush">
        <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/stickfromthebush/MainActivity.kt

package jp.aosystem.stickfromthebush

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.*
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
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.stickfromthebush.databinding.ActivityMainBinding
import java.util.*
import kotlin.random.Random


class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: ConstraintLayout
    //
    private val levels: List<Int> = listOf(
            160,   //level0
            80,    //level1
            70,    //level2
            60,    //level3
            50,    //level4
            45,    //level5
            40,    //level6
            35,    //level7
            30,    //level8
            25,    //level9
            20,   //level10
    )
    private val girlXs: List<Float> = listOf(  //stick girl の横位置
            100F,
            240F,
            380F,
            520F,
            660F,
            800F,
    )
    private val stickY: Float = 392F
    private val girlStandY: Float = 301F
    private val girlDownY: Float = 500F
    private val bushForeXYs: List<Pair<Float,Float>> = listOf(
        Pair(17F,315F),
        Pair(158F,326F),
        Pair(310F,325F),
        Pair(446F,315F),
        Pair(595F,335F),
        Pair(725F,325F),
    )
    private var bushForeXs: Array<Float> = arrayOf(     //基準位置としてセットされる
        0F,
        0F,
        0F,
        0F,
        0F,
        0F,
    )
    data class ResourceImg (
            var img: Int,
            var w: Int,
            var h: Int,
    )
    private val resourceBushBack: ResourceImg = ResourceImg(R.drawable.ic_bush_back, 900 ,900)
    private val resourceBushFores: List<ResourceImg> = listOf(
        ResourceImg(R.drawable.ic_bush_fore1, 159 ,217),
        ResourceImg(R.drawable.ic_bush_fore2, 162 ,205),
        ResourceImg(R.drawable.ic_bush_fore3, 138 ,205),
        ResourceImg(R.drawable.ic_bush_fore4, 151 ,218),
        ResourceImg(R.drawable.ic_bush_fore5, 144 ,197),
        ResourceImg(R.drawable.ic_bush_fore6, 152 ,208),
    )
    private val resourceStick: ResourceImg = ResourceImg(R.drawable.ic_stick,22,102)
    private val resourceGirlStand: ResourceImg = ResourceImg(R.drawable.ic_girl_stand,127,376)
    private val resourceGirlDown: ResourceImg = ResourceImg(R.drawable.ic_girl_down,190,260)
    private var bushForeLayout: Array<LinearLayout> = arrayOf()
    private lateinit var stickLayout: LinearLayout
    private lateinit var girlStandLayout: LinearLayout
    private lateinit var girlDownLayout: LinearLayout
    private var baseWidth: Int = 0      //描画エリア
    private var baseHeight: Int = 0     //描画エリア
    private var viewRatio: Float = 0F   //画像縮小率
    private var marginLeft: Float = 0F  //左側マージン
    private var destroyFlag: Boolean = false    //Activity破棄された場合などはtrue
    private var busyFlag: Boolean = false    //PLAY中はtrue
    private var difficultyLevel: Int = 1       //難易度
    private var themeNumber: Int = 0    //テーマの種類
    private var localeLanguage: String = ""
    private var positionStick: Int = 0  //初期立ち位置
    private var positionGirl: Int = 4  //初期立ち位置
    private var timelineCount: Int = 0  //タイムラインのループでカウントアップしていく
    private var timelineSubCount: Int = 0   //タイムラインの中で使用される1回の動作
    private var score: Int = 0      //ゲームのSCORE
    private var lastResultBackColor: Boolean = false

    //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 DIFFICULTY_LEVEL: String = "difficultyLevel"
        internal const val SETTINGS: String = "settings"
        internal const val THEME_NUMBER: String = "themeNumber"
        internal const val LOCALE_LANGUAGE: String = "localeLanguage"
    }

    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.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.init()
            }
        })
    }

    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 init() {
        this.binding.layoutBase.removeAllViews()
        if (this.baseWidth.div(this.baseHeight) < this.resourceBushBack.w.div(this.resourceBushBack.h)) { //横がぴったり
            this.viewRatio = this.baseWidth.div(this.resourceBushBack.w.toFloat())
            this.marginLeft = 0F
        } else {    //横に隙間
            this.viewRatio = this.baseHeight.div(this.resourceBushBack.h.toFloat())
            this.marginLeft = (this.baseWidth - this.resourceBushBack.w.toFloat().times(this.viewRatio)).div(2)
        }
        //bush back
        val bushBackLayout: LinearLayout = LinearLayout(this)
        bushBackLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        bushBackLayout.x = this.marginLeft
        bushBackLayout.y = 0F
        val bushBackWidth: Float = this.resourceBushBack.w.toFloat().times(this.viewRatio)
        val bushBackHeight: Float = this.resourceBushBack.h.toFloat().times(this.viewRatio)
        val imgBushBack: ImageView = ImageView(this)
        imgBushBack.setImageResource(this.resourceBushBack.img)
        bushBackLayout.addView(imgBushBack, bushBackWidth.toInt(), bushBackHeight.toInt())
        this.binding.layoutBase.addView(bushBackLayout)
        //bush fore
        this.bushForeLayout = arrayOf()
        for (i in 0 until resourceBushFores.size) {
            this.bushForeLayout += LinearLayout(this)
        }
        for (i in 0 until resourceBushFores.size) {
            this.bushForeLayout[i].layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT)
            val (bushForeX,bushForeY) = this.bushForeXYs[i]
            this.bushForeLayout[i].x = bushForeX.times(this.viewRatio) + this.marginLeft
            this.bushForeLayout[i].y = bushForeY.times(this.viewRatio)
            val bushForeWidth: Float = this.resourceBushFores[i].w.toFloat().times(this.viewRatio)
            val bushForeHeight: Float = this.resourceBushFores[i].h.toFloat().times(this.viewRatio)
            val imgBushFore: ImageView = ImageView(this)
            imgBushFore.setImageResource(this.resourceBushFores[i].img)
            this.bushForeLayout[i].addView(imgBushFore, bushForeWidth.toInt(), bushForeHeight.toInt())
            this.binding.layoutBase.addView(this.bushForeLayout[i])
            this.bushForeXs[i] = this.bushForeLayout[i].x  //基準位置登録
        }
        //stick
        this.stickLayout = LinearLayout(this)
        this.stickLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        this.stickLayout.y = this.stickY.times(this.viewRatio)
        val stickWidth: Float = this.resourceStick.w.toFloat().times(this.viewRatio)
        val stickHeight: Float = this.resourceStick.h.toFloat().times(this.viewRatio)
        val imgStick: ImageView = ImageView(this)
        imgStick.setImageResource(this.resourceStick.img)
        this.stickLayout.addView(imgStick, stickWidth.toInt(), stickHeight.toInt())
        this.binding.layoutBase.addView(this.stickLayout)
        //girlStand
        this.girlStandLayout = LinearLayout(this)
        this.girlStandLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        this.girlStandLayout.y = this.girlStandY.times(this.viewRatio)
        val girlStandWidth: Float = this.resourceGirlStand.w.toFloat().times(this.viewRatio)
        val girlStandHeight: Float = this.resourceGirlStand.h.toFloat().times(this.viewRatio)
        val imgGirlStand: ImageView = ImageView(this)
        imgGirlStand.setImageResource(this.resourceGirlStand.img)
        this.girlStandLayout.addView(imgGirlStand, girlStandWidth.toInt(), girlStandHeight.toInt())
        this.binding.layoutBase.addView(this.girlStandLayout)
        //girlDown
        this.girlDownLayout = LinearLayout(this)
        this.girlDownLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        this.girlDownLayout.y = this.girlDownY.times(this.viewRatio)
        val girlDownWidth: Float = this.resourceGirlDown.w.toFloat().times(this.viewRatio)
        val girlDownHeight: Float = this.resourceGirlDown.h.toFloat().times(this.viewRatio)
        val imgGirlDown: ImageView = ImageView(this)
        imgGirlDown.setImageResource(this.resourceGirlDown.img)
        this.girlDownLayout.addView(imgGirlDown, girlDownWidth.toInt(), girlDownHeight.toInt())
        this.girlDownLayout.alpha = 0F
        this.binding.layoutBase.addView(this.girlDownLayout)
        //
        this.stickPosition(false)
        this.girlStandPosition()
        //result表示
        this.binding.textResult.text = "LEVEL: " + this.difficultyLevel.toString()
    }

    private fun stickPosition(pushPull: Boolean) {
        if (pushPull) {
            this.stickLayout.x =this.girlXs[this.positionStick].times(this.viewRatio) + this.marginLeft - this.resourceStick.w.times(this.viewRatio).div(2)
            this.stickLayout.alpha = 1F
        } else {
            this.stickLayout.alpha = 0F
        }
    }

    private fun girlStandPosition() {
        this.girlStandLayout.x = this.girlXs[this.positionGirl].times(this.viewRatio) + this.marginLeft - this.resourceGirlStand.w.times(this.viewRatio).div(2)
    }

    private fun girlDownPosition() {
        this.girlDownLayout.x = this.girlXs[this.positionGirl].times(this.viewRatio) + this.marginLeft - this.resourceGirlDown.w.times(this.viewRatio).div(2)
    }

    fun onClickGirlLeft(v: View) {
        if (this.timelineSubCount in 11..15 && this.positionStick == this.positionGirl - 1) {
            return
        }
        if (this.positionGirl > 0) {
            this.positionGirl -= 1
            this.girlStandPosition()
        }
    }

    fun onClickGirlRight(v: View) {
        if (this.timelineSubCount in 11..15 && this.positionStick == this.positionGirl + 1) {
            return
        }
        if (this.positionGirl < (this.resourceBushFores.size - 1)) {
            this.positionGirl += 1
            this.girlStandPosition()
        }
    }

    fun onClickStart(v: View) {
        if (this.busyFlag) {
            return
        }
        this.busyFlag = true
        this.binding.textStart.alpha = 0F
        this.binding.textSetting.alpha = 0F
        this.init()
        this.destroyFlag = false
        this.timelineCount = 0
        this.timelineSubCount = -999
        this.score = 0
        this.resultBackColor(true)
        this.resultBackColor(false)
        this.actionTimeline()
    }

    private fun resultText(tick: Int) {
        val tick2: Int = tick.div(3)
        val progressLeft: String = if (this.busyFlag) ".".repeat(10 - tick2) + "|".repeat(tick2) else ""
        val progressRight: String = if (this.busyFlag) "|".repeat(tick2) + ".".repeat(10 - tick2) else ""
        this.binding.textResult.text = progressLeft + " LEVEL: " + this.difficultyLevel.toString() + " SCORE: " + this.score + " " + progressRight
    }

    private fun resultBackColor(onOff: Boolean) {
        if (this.lastResultBackColor == onOff) {
            return
        }
        this.lastResultBackColor = onOff
        val c: Int = when (this.busyFlag) {
            true -> if (this.lastResultBackColor) ContextCompat.getColor(this, R.color.resultHit) else ContextCompat.getColor(this, R.color.resultNormal)
            false -> ContextCompat.getColor(this, R.color.resultEnd)
        }
        this.binding.textResult.setBackgroundColor(c)
    }

    private fun actionTimeline() {
        this.timelineCount += 1
        this.timelineSubCount += 1
        Handler(Looper.getMainLooper()).postDelayed({
            if (this.destroyFlag) {
                return@postDelayed
            }
            if (timelineCount % 30 == 0) {
                this.timelineSubCount = 0
                this.positionStick = Random.nextInt(this.resourceBushFores.size)    //位置決め
            }
            if (this.timelineSubCount in 0..10) {   //bush fore 揺らす
                this.bushForeVibration(this.timelineSubCount)
            }
            if (this.timelineSubCount == 11) {   //stick出す
                this.stickPushPull(true)
            }
            if (this.timelineSubCount == 15) {   //stick引く
                this.stickPushPull(false)
            }
            if (this.timelineSubCount >= 30) {
                this.timelineSubCount = 0
            }
            this.resultText(timelineCount % 30)
            actionTimeline()
        }, this.levels[difficultyLevel].toLong())
    }

    private fun bushForeVibration(num: Int) {
        var vib: Float = 5.times(this.viewRatio)
        if (num % 2 == 0) {
            vib = - vib
        }
        if (num == 10) {
            vib = 0F
        }
        this.bushForeLayout[this.positionStick].x = this.bushForeXs[this.positionStick] + vib
    }
    private fun stickPushPull(pushPull: Boolean) {
        this.stickPosition(pushPull)
        if (pushPull) {
            if (this.positionStick == this.positionGirl) {
                this.gameOver()
            } else {
                this.score += 1
            }
        }
    }

    private fun gameOver() {
        this.destroyFlag = true
        this.busyFlag = false
        this.binding.textStart.alpha = 1F
        this.binding.textSetting.alpha = 1F
        this.resultBackColor(true)
        this.girlStandLayout.alpha = 0F
        this.girlDownPosition()
        this.girlDownLayout.alpha = 1F
    }

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

    fun onClickSetting(v: View) {
        if (this.busyFlag) {
            return
        }
        this.destroyFlag = true
        val intent = Intent(applicationContext, SettingActivity::class.java)
        intent.putExtra(DIFFICULTY_LEVEL, this.difficultyLevel)
        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 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)
    }

    //テーマを保存
    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/stickfromthebush/SettingActivity.kt

package jp.aosystem.stickfromthebush

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.stickfromthebush.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 themeNumber: Int = intent.getIntExtra(MainActivity.THEME_NUMBER,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.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 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.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: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:background="#7CB342"
            android:orientation="horizontal">

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

            <TextView
                android:id="@+id/textStart"
                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>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textResult"
                android:layout_width="match_parent"
                android:layout_height="36dp"
                android:background="#81C784"
                android:gravity="center" />
        </LinearLayout>

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

        </FrameLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/textLeft"
                android:layout_width="wrap_content"
                android:layout_height="48dp"
                android:layout_weight="1"
                android:background="#64B5F6"
                android:gravity="center"
                android:onClick="onClickGirlLeft"
                android:text="@string/left" />

            <TextView
                android:id="@+id/textRight"
                android:layout_width="wrap_content"
                android:layout_height="48dp"
                android:layout_weight="1"
                android:background="#FF8A65"
                android:gravity="center"
                android:onClick="onClickGirlRight"
                android:text="@string/right" />
        </LinearLayout>

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




    </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"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="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="15dp">

                <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/switchTheme"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="@string/darkTheme" />

            <View
                android:id="@+id/divider5"
                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/divider2"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="10dp"
                android:background="?android:attr/listDivider" />

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

            <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:adjustViewBounds="true"
                app:srcCompat="@drawable/ic_usage1" />

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

        </LinearLayout>
    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>