2026-04-06

【Kotlin】自定義 app 結束對話框

本筆記搭配使用 ViewBinding, AdMob, Edge-to-Edge


app  層級 build.gradle
 
android {
    
    compileSdk {
        version = release(36)
    }
...
...

    buildFeatures {
        viewBinding = true
    }
    
    defaultConfig {
        ...
        ...
        minSdk = 26
        targetSdk = 36
    }
}

...
...

dependencies {

    ...
    ...
    
    // adMob
    implementation("com.google.android.gms:play-services-ads:25.1.0")
}
 


strings.xml
 
<resources>
    
    <string name="dialog_title">確定要離開?</string>
    <string name="dialog_message">您確定要關閉應用程式嗎?</string>
    <string name="btn_cancel">取消</string>
    <string name="btn_exit">離開</string>
    
    ...
    ...
 
    <!-- adMob app id 測試 -->
    <string name="admob_app_id">ca-app-pub-3940256099942544~3347511713</string>
    <!-- 橫幅 測試 -->
    <string name="banner_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>
 
</resources>
 


AndroidManifest.xml
 
<manifest
 
    <!-- 存取網路連線 -->
    <uses-permission android:name="android.permission.INTERNET" />
 
    ...
    ...
 
    <application  ... >
 
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="@string/admob_app_id" />
 
        ...
        ...
 
        <!-- AdMob 的 AdActivity -->
        <activity
            android:name="com.google.android.gms.ads.AdActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:theme="@android:style/Theme.Translucent" />
 
        ...
        ...
 
    </application>
 
</manifest>
 


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:id="@+id/main"    ← 注意這裡
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
 


自定義對話框 dialog_exit.xml,因為版面很單純,所以採用 LinearLayout
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="24dp">

    <TextView
        android:id="@+id/tv_dialog_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/dialog_title"
        android:textSize="20sp"
        android:textStyle="bold"
        android:textColor="?attr/colorOnSurface"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:gravity="end"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_cancel"
            style="?attr/borderlessButtonStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btn_cancel" />

        <Button
            android:id="@+id/btn_exit"
            style="?attr/borderlessButtonStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="@string/btn_exit"
            android:textColor="?attr/colorError"/>

    </LinearLayout>

    <!-- 做為 banner 的容器 -->
    <FrameLayout
        android:id="@+id/adContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:minHeight="250dp" />
        
</LinearLayout>
 


MainActivity.kt
 
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    
    private var preloadedAdView: AdView? = null    // 預載的廣告物件
    

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        // 設定返回鍵攔截(支援新舊所有 Android 版本)
        setupBackPressedListener()
        
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        
        // 對話框的 AdMob banner
        preloadBannerAd()
    }
override fun onPause() { preloadedAdView?.pause() super.onPause() } override fun onResume() { super.onResume() preloadedAdView?.resume() } override fun onDestroy() { preloadedAdView?.destroy() super.onDestroy() } private fun setupBackPressedListener() { val callback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { // 當使用者按下返回鍵(或手勢返回)時,顯示對話框 showExitCustomDialog() } } // 將 callback 加到 Dispatcher 中,它會自動處理生命週期 onBackPressedDispatcher.addCallback(this, callback) } private fun showExitCustomDialog() { // 初始化 Dialog 的 View Binding val dialogBinding = DialogExitBinding.inflate(layoutInflater) // 將預載好的 AdView 放入對話框容器中 preloadedAdView?.let { adView -> // 避免 AdView 重複被加入不同的 Parent (adView.parent as? ViewGroup)?.removeView(adView) dialogBinding.adContainer.addView(adView) } val dialog = MaterialAlertDialogBuilder(this) .setView(dialogBinding.root) .setCancelable(true) .create() dialogBinding.btnCancel.setOnClickListener { dialog.dismiss() } dialogBinding.btnExit.setOnClickListener { dialog.dismiss() finish() } dialog.show() } private fun preloadBannerAd() { preloadedAdView = AdView(this).apply { adUnitId = getString(R.string.banner_ad_unit_id) setAdSize(AdSize.MEDIUM_RECTANGLE) // 開始非同步載入 loadAd(AdRequest.Builder().build()) } } }  

實際的對話框效果


沒有留言:

張貼留言